How to use ICloneable<T> when T is List<T>?
Asked Answered
C

4

5

I have the following:

    public class InstanceList : List<Instance> {}

I would like to make this cloneable. Following the example here: Why no ICloneable<T>?

I tried the following:

    public interface ICloneable<T> : ICloneable Where T : ICloneable<T>
           {        new T Clone();    }

    public class InstanceList : List<Instance>, ICloneable<List<Instance>>  {}

But I get a compiler error. The error message states that List<Instance> must be convertible to ICloneable<List<Instance>> in order to use parameter T in the generic interface ICloneable<T>.

What am I missing here?

Carvelbuilt answered 2/8, 2011 at 18:43 Comment(0)
D
4

You can't do this, because you can't define List<T> yourself. You would only be able to do this if you could declare your own List<T> because of the way you've constrained ICloneable<T>. Since List<T> truly doesn't implement ICloneable<T>, you're going to have to have the type of T be InstanceList instead, which you do have control over.

Here's how you would implement it:

public class InstanceList : List<Instance>, ICloneable<InstanceList>
{
    public InstanceList Clone()
    {
        // Implement cloning guts here.
    }

    object ICloneable.Clone()
    {
        return ((ICloneable<InstanceList>) this).Clone();
    }
}

public class Instance
{

}

public interface ICloneable<T> : ICloneable where T : ICloneable<T>
{
    new T Clone();
}

Of course, there is another alternative you could do. You could widen your generics a little bit, to create a CloneableList<T> type:

public class CloneableList<T> : List<T>, ICloneable<CloneableList<T>>
{
    public CloneableList<T> Clone()
    {
        throw new InvalidOperationException();
    }

    object ICloneable.Clone()
    {
        return ((ICloneable<CloneableList<T>>) this).Clone();
    }
}

public interface ICloneable<T> : ICloneable where T : ICloneable<T>
{
    new T Clone();
}

And if you really want to get fancy, create something that restricts T to ICloneable. Then you could implement ICloneable on the Instance class, and anything else you want to include in an ICloneable<T> list, thus treating every CloneableList<T> in the exact same way, avoiding a different implementation of ICloneable<T> for each and every cloneable list you want to create.

public class CloneableList<T> : List<T>, ICloneable<CloneableList<T>> where T : ICloneable
{
    public CloneableList<T> Clone()
    {
        var result = new CloneableList<T>();
        result.AddRange(this.Select(item => (T) item.Clone()));
        return result;
    }

    object ICloneable.Clone()
    {
        return ((ICloneable<CloneableList<T>>) this).Clone();
    }
}

public interface ICloneable<T> : ICloneable where T : ICloneable<T>
{
    new T Clone();
}
Dunaway answered 2/8, 2011 at 18:49 Comment(7)
this worked. one change: T Clone(); requires new keyword to override object.Clone(). I see now where my thinking was incorrect. I am cloning the class not List<Instance>. Thanks!Carvelbuilt
Be sure to check out the edits I've made. The second and third options are far more flexible than the first I listed.Dunaway
Why does it require the second object ICloneable.Clone() method? I thought this interface would require just one method for the the Clone() operation.Carvelbuilt
Because you're implementing ICloneable in addition to ICloneable<T>, the second implementation is needed to satisfy ICloneable. It's called an "explicit interface method implementation", which basically means, it's considered, for all practical purposes, private, unless you're holding a reference to the object with a type of "ICloneable" (without the T), then the call to the "Clone()" method must return an object. Read this: msdn.microsoft.com/en-us/library/ms173157.aspx . Basically, you have to satisfy ICloneable and ICloneable<T>.Dunaway
The 3rd edit works very slick. Question about the second override to satisfy ICloneable; why not simply this: object ICloneable.Clone() { return Clone();}Carvelbuilt
I believe you can do return this.Clone() as well. Doesn't really matter. I casted to ICloneable<T> just to make it clear which implementation I'm calling. I find calling this.Clone() is a little fuzzy for me to think about.Dunaway
A slight improvement would be to declare the interface as ICloneable<out T>. It's arguable whether T should be constrainted to ICloneable<T>; I would suggest that it should not, since there are some inheritance scenarios where a routine might want to accept several types of cloneable objects which independently derive from an object which does not expose a public clone function.Crest
P
2

The problem is your generic constraint where T : IClonable<T>. Because you're "instantiating" your interface as ICloneable<List<Instance>>, List<Instance> is your T, and so the generic constraint translates to where List<Instance> : IClonable<List<Instance>>. List<Instance> does not fulfill that constraint.

Perhaps you're trying to do something like this:

public interface ICloneableList<T> : ICloneable where T : ICloneable
{
}
Periphrastic answered 2/8, 2011 at 18:48 Comment(3)
If I do it this way I get `public class InstanceList : List<Instance>, ICloneable { public object Clone() { }}'Carvelbuilt
...and that returns type object instead of type List<Instance>Carvelbuilt
I omitted the method, but it would still need it. I was just pointing out that the generic constraint was wrong.Periphrastic
S
1

To add to the other good answers already there - when you clone, you expect to get an identical copy back, right? So instead of:

public class InstanceList : List<Instance>, ICloneable<List<Instance>>  {}

Shouldn't it actually be:

public class InstanceList : List<Instance>, ICloneable<InstanceList>  {}

That way you will also get no compiler errors.

Statuary answered 2/8, 2011 at 18:59 Comment(1)
right, that is the answer that resolves the error. In the spirit of the community I think David is showing me a much better way to implement this, he originally pointed out what you show above.Carvelbuilt
C
0

I don't think you can really do what you want. While it is useful not to require the type argument of ICloneable<T> to implement ICloneable<T>, I don't think the List<T> class can be very well extended to support cloning since it does not provide any means of detaching or duplicating the array which holds all the data items, does not allow a subclass access to that array, and does not allow a subclass to override enough virtual methods to render the array irrelevant. Although clone methods should start by using MemberwiseClone (to ensure that the cloned object is the same type as the original), there would be no guaranteed way to force the newly-cloned list to create a new array to hold its objects without disturbing the old one.

The closest thing I can suggest to doing what you want would be to define an ICloneableList<T> which inherits from IList<T> and ICloneable<IList<T>> and define an CloneableList class which implementats that by wrapping a list. Cloning a CloneableList should create a new List<T> with items copied from the old one, which can be done by using the appropriate constructor for the new List.

Crest answered 22/9, 2011 at 17:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.