简体   繁体   中英

C# typesafe cloning with generics

I'm trying to create a generic container object (done; no problem) which can be deep cloned. That means it has to deep clone its contained objects, and it's this that is causing me problems. Here's some code:

    public abstract class VehiclePart {
        public abstract T cloneTypesafe<T>()
                        where T : VehiclePart, new() // L1
    }


    public class Gearstick : VehiclePart { // L2

        public Gearstick() { }

        public override T cloneTypesafe<T>() {
            var res2 = new Gearstick();
            return res2; // L3
            return (T)res2; // L4
        }
    }

It rejects this (comment L3) saying “Cannot implicitly convert type 'LDB.Program.Gearstick' to 'T'”, but that makes no sense because T is explicitly derived from VehiclePart (Line L1), and Gearstick is explicitly derived from VehiclePart (line L2), so the conversion should be entirely legal.

Even if I had an explicit cast (line L4) it's not happy ("Cannot convert type 'LDB.Program.Gearstick' to 'T'"), so what's the problem and how do I fix this? (I don't want to use reflection, which seems to be a common suggestion in answers to similar questions. I have read several comments saying not to use ICloneable).

With C# 9 you can remove the need of generics here and use covariant return types :

public abstract class VehiclePart
{
    public abstract VehiclePart cloneTypesafe();
}    

public class Gearstick : VehiclePart {

    public Gearstick() { }

    public override Gearstick cloneTypesafe()
    {
        return new Gearstick(); // your actual implementation
    }
}

so what's the problem

The signature of your method allows me to call your code as follows:

CarWindow myCarWindow = myGearstick.cloneTypesafe<CarWindow>();

This is a valid call, since CarWindow is a subtype of VehiclePart . Still, it should be obvious that this call is nonsensical.


and how do I fix this?

With C# 9.0 or newer, you can use covariant return types, see Guru Stron's answer .

If you are stuck with earlier versions, see Jamiec's answer for a possible solution. Alternatively, you could extract the method into your own IDeepCloneable<T> interface for separation of concerns:

public interface IDeepCloneable<T>
{
    T CloneTypesafe();
}

public class Gearstick : VehiclePart, IDeepCloneable<Gearstick>
{
    public Gearstick CloneTypesafe() { ... }
}

Note that, as Jamiec pointed out in the comments, the type system won't stop you from making nonsensical declarations such as public class Gearstick: VehiclePart, IDeepClonable<Windscreen> .

This is a misuse of generics. If you use generics, you are saying that the caller of the method can pass in whatever type they want (with some constraints), and your method will still work. This is clearly not the case here. Your cloneTypesafe method in Gearstick will only work if T is Gearstick . If T is, eg Engine , or Wheel , then the cast will fail.

cloneTypesafe should really return "the type of the class that it is in", but we have no way of saying that in C#. You can do this as a work around though:

public abstract class VehiclePart<T> 
    where T : VehiclePart<T>, new(){ // implementers are supposed to replace T with their class
    public abstract T cloneTypesafe();
}

// you are not "forced" to say ": VehiclePart<Gearstick>", hence why this is only a workaround
public class Gearstick : VehiclePart<Gearstick> {
    public override Gearstick cloneTypesafe() {
        var res2 = new Gearstick();
        // copy some properties...
        return res2;
    }
}

For this to work as (I think) you expected, you should have the generic definition on the abstract class itself

public abstract class VehiclePart<T> where T: new() {
    public abstract T cloneTypesafe();
}

public class Gearstick : VehiclePart<Gearstick> { // L2

    public Gearstick() { }

    public override Gearstick cloneTypesafe() {
        var res2 = new Gearstick();
        return res2; 
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM