C# Deriving from Inherited Generics
-
I've got a small program I'm trying to write that will cover a part of several versions of a database layout to help make it easier to copy data from one database to another, which I need to do fairly frequently. Copying shouldn't ever need to occur across versions, though if it does, I can just modify the SQL script that I currently use for this task. I figured that inheritance should make it fairly easy to deduplicate code, since later versions of the database just add columns and at one point, a couple new tables. Because this covers several database versions and will connect to a user's selection out of >100 database instances, database-first EF isn't really going to be useful. (I also don't need to read in all the columns in all the relevant tables in order to copy the necessary data.
My structure looks a bit like this (though with more data):
public abstract class Basic<T> where T : Basic<T>, new() { public Decimal field_id { get; set; } public String field_1 { get; set; } public List<ItemProperties> oneToMany { get; set; } public Basic() { } public Basic(Decimal id) { loadDataFromDatabaseById(id); } protected virtual void loadDataFromDatabaseById(Decimal id); public virtual T makeCopy() { //get new field_id from sequence //copy field_1 and oneToMany (deep copy) } } public class DerivedA : Basic<DerivedA> { protected override void loadDataFromDatabaseById(Decimal id) { //read in field_id, field_1, and oneToMany from (old) Oracle DB } } public class DerivedB : Basic<DerivedB> { protected override DerivedB makeCopy() { //doesn't use actual sequence to get new field_id; instead has sequence tables, so those need updated when making a copy //copy field_2, oneToMany (deep copy) } } public class Derived1 : Basic<Derived1> { protected override void loadDataFromDatabaseById(Decimal id) { //read in field_id, field_1, and oneToMany from MS SQL Server DB } } public class Derived2 : Derived1 { String field_2 { get; set; } List<ItemConfigurations> oneToMany2 { get; set; } protected override void loadDataFromDatabaseById(Decimal id) : base(id) { //also read in field_2 and oneToMany2 from MS SQL Server DB } public override Derived2 makeCopy() // <-- error here: 'Derived2.makeCopy()': return type must be 'Derived1' to match overridden member 'Basic<Derived1>.makeCopy()' { //get new field_id from sequence //copy field_1, field_2, oneToMany (deep copy), and oneToMany2 (deep copy) } }
What do I need to do to fix the error on Derived2.makeCopy()?
-
@djls45 said in C# Deriving from Inherited Generics:
What do I need to do to fix the error on Derived2.makeCopy()?
Exactly what the error says: make the declared return type
Derived1
.What you're trying to do is known as "covariant return types", and it's currently under discussion but not supported in the C# language and not planned to be supported anytime in the immediate future.
-
This post is deleted!
-
@Mason_Wheeler So I should just strip out the generics stuff? My whole reason for adding it in was to be able to use inheritance so the calling code wouldn't need to check and cast the values in order to use them and to guarantee that each
makeCopy()
would return the same type as its class. You're saying this is currently impossible? Would copy constructors be a better option for that, then? The reason I didn't go with those was because I have to set the database info at the very beginning of each constructor, which I can't do in C# while still deriving from thebase(...)
constructor.
-
I may be going to hell for this.
static void Main() { var source = new Derived2("djls45"); Console.WriteLine($"Source is {source}"); Derived2 target = source.makeCopy(); Console.WriteLine($"Target is {target}"); } public abstract class Basic<T> where T : Basic<T>, new() { public virtual Convertable<D> makeCopy<D>() where D: T { return null; } } public class Derived1 : Basic<Derived1> { } public class Derived2 : Derived1 { Guid _id; public string Name { get; private set; } public Derived2(string name){ _id = Guid.NewGuid(); Name = name; } public virtual Convertable<Derived2> makeCopy() { return new Derived2(this.Name); } public override string ToString() { return $"{{Derived2 with _id={_id}, Name={Name}}}"; } } public struct Convertable<T> { T inner; public static implicit operator T (Convertable<T> c) { return c.inner; } public static implicit operator Convertable<T>(T t) { return new Convertable<T>{ inner = t }; } }
-
@djls45 said in C# Deriving from Inherited Generics:
@Mason_Wheeler So I should just strip out the generics stuff? My whole reason for adding it in was to be able to use inheritance so the calling code wouldn't need to check and cast the values in order to use them and to guarantee that each
makeCopy()
would return the same type as its class. You're saying this is currently impossible? Would copy constructors be a better option for that, then? The reason I didn't go with those was because I have to set the database info at the very beginning of each constructor, which I can't do in C# while still deriving from thebase(...)
constructor.I don't speak C#, so apply large doses of salt:
The inheritance hierarchy seems to be
Derived2
->Derived1
->Basic<Derived1>
. That means the parameterT
for the Generic isDerived1
, sopublic virtual T makeCopy()
gets instantiated as
public virtual Derived1 makeCopy()
Like the error message suggests, this is the interface you declared and the virtual function you need to implement. As @Mason_Wheeler said, C# seems to not support covariant return types, but I think that's just a bit more than syntactic sugar. You could still declare your class as returning
Derived1
as per the interface and actually return an object of typeDerived2
. (If I'm not completely wrong, C# deals with references, so there's no slicing involved that would break things)Now, at the calling site, there's two options. Either you have an object of static type
Derived2
and you thus know the method will make such a copy, then it is safe to cast back the return value toDerived2
. (This is the part where language support for it is a bit more than syntactic sugar, as the compiler would prove this) If you have an object that's of dynamic typeDerived2
but static typeDerived1
at the call site, then Liskov applies and you can't do anything with it other than treat the returned copy asDerived1
.Also, if that's what you're worried about, calling
makeCopy()
twice in a row from such aDerived2
should still correctly return aDerived2
even if nominally declared asDerived1
.Hope that made sense.
-
Use RedGate instead of rolling your own?
-
@mikehurley said in C# Deriving from Inherited Generics:
Use RedGate instead of rolling your own?
RedGate no longer licenses their SDK.
-
@Unperverted-Vixen said in C# Deriving from Inherited Generics:
@mikehurley said in C# Deriving from Inherited Generics:
Use RedGate instead of rolling your own?
RedGate no longer licenses their SDK.
I wasn't aware of a SDK. Don't they have a desktop app?
-
@mikehurley Yes, but that doesn't help @djls45 trying to do this programmatically.
-
@topspin said in C# Deriving from Inherited Generics:
You could still declare your class as returning
Derived1
as per the interface and actually return an object of typeDerived2
. (If I'm not completely wrong, C# deals with references, so there's no slicing involved that would break things)Now, at the calling site, there's two options. Either you have an object of static type
Derived2
and you thus know the method will make such a copy, then it is safe to cast back the return value toDerived2
. (This is the part where language support for it is a bit more than syntactic sugar, as the compiler would prove this) If you have an object that's of dynamic typeDerived2
but static typeDerived1
at the call site, then Liskov applies and you can't do anything with it other than treat the returned copy asDerived1
.Also, if that's what you're worried about, calling
makeCopy()
twice in a row from such aDerived2
should still correctly return aDerived2
even if nominally declared asDerived1
.Hope that made sense.
It does, and I could do all that just by making
makeCopy()
return aBasic
without any generics.I wanted to try to abstract/consolidate the
makeCopy()
code while still having it return the same type as the current object.
-
This works:
public interface Copyable<in T> { void makeCopy<D>(out D d) where D: T; } static void Main() { var source = new Derived2("djls45"); Console.WriteLine($"Source is {source}"); source.makeCopy(out var target); Console.WriteLine($"Target is {target}"); // Calls correct ToString() Console.WriteLine($"Target.GetType() is {target.GetType()}"); // Derived2 } public abstract class Basic<T> where T : Basic<T>, Copyable<T>, new() { public virtual void makeCopy<D>(out D d) where D: T { d = null; } public override string ToString() => "This is Basic, I'm afraid"; } public class Derived1 : Basic<Derived1>, Copyable<Derived1> { public override string ToString() => "This is Derived1, I'm afraid"; } public class Derived2 : Derived1 { Guid _id; public string Name { get; private set; } public Derived2(string name) { _id = Guid.NewGuid(); Name = name; } public virtual void makeCopy(out Derived2 d){ d = new Derived2(this.Name); } public override string ToString() => $"{{Derived2 with _id={_id}, Name={Name}}}"; }
It requires changing the signature of makeCopy, but I guess you already expected that.
-
On the other hand if I try to refer to type Basic<Derived2> I get a
CS0425 The constraints for type parameter 'D' of method 'Basic<Derived1>.makeCopy<D>(out D)' must match the constraints for type parameter 'D' of interface method 'Copyable<Derived2>.makeCopy<D>(out D)'. Consider using an explicit interface implementation instead.
I'm not sufficiently awake yet to understand what this means.
-
@Zecc it's saying you've shadowed it.
-
I think I just have to go without generics, so I've followed a simple inheritance model with the return type for each overriding method being the base class, but each implementation actually creating and returning an object of its own derived class:
class Derived2 : Derived1 { //... public Basic makeCopy() { return new Derived2(/*...*/); } }
If it helps any, I also have
class Derived3 : Derived2
andclass Derived4 : Derived3
, each with their ownBasic makeCopy()
that returns aDerived3
andDerived4
object, respectively.