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.


  • 🚽 Regular

    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 the base(...) constructor.


  • 🚽 Regular

    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 }; }
    }

  • BINNED

    @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 the base(...) 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 parameter T for the Generic is Derived1, so

    public 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 type Derived2. (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 to Derived2. (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 type Derived2 but static type Derived1 at the call site, then Liskov applies and you can't do anything with it other than treat the returned copy as Derived1.

    Also, if that's what you're worried about, calling makeCopy() twice in a row from such a Derived2 should still correctly return a Derived2 even if nominally declared as Derived1.

    Hope that made sense.


  • Trolleybus Mechanic

    Use RedGate instead of rolling your own?


  • And then the murders began.

    @mikehurley said in C# Deriving from Inherited Generics:

    Use RedGate instead of rolling your own?

    RedGate no longer licenses their SDK.


  • Trolleybus Mechanic

    @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?


  • And then the murders began.

    @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 type Derived2. (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 to Derived2. (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 type Derived2 but static type Derived1 at the call site, then Liskov applies and you can't do anything with it other than treat the returned copy as Derived1.

    Also, if that's what you're worried about, calling makeCopy() twice in a row from such a Derived2 should still correctly return a Derived2 even if nominally declared as Derived1.

    Hope that made sense.

    It does, and I could do all that just by making makeCopy() return a Basic 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.


  • 🚽 Regular

    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.


  • 🚽 Regular

    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 and class Derived4 : Derived3, each with their own Basic makeCopy() that returns a Derived3 and Derived4 object, respectively.


Log in to reply