Why no ICloneable?

2019-01-04 08:15发布

Is there a particular reason why a generic ICloneable<T> does not exist?

It would be much more comfortable, if I would not need to cast it everytime I clone something.

9条回答
Summer. ? 凉城
2楼-- · 2019-01-04 08:38

ICloneable is considered a bad API now, since it does not specify whether the result is a deep or a shallow copy. I think this is why they do not improve this interface.

You can probably do a typed cloning extension method, but I think it would require a different name since extension methods have less priority than original ones.

查看更多
冷血范
3楼-- · 2019-01-04 08:39

A big problem is that they could not restrict T to be the same class. Fore example what would prevent you from doing this:

interface IClonable<T>
{
    T Clone();
}

class Dog : IClonable<JackRabbit>
{
    //not what you would expect, but possible
    JackRabbit Clone()
    {
        return new JackRabbit();
    }

}

They need a parameter restriction like:

interfact IClonable<T> where T : implementing_type
查看更多
冷血范
4楼-- · 2019-01-04 08:43

I need to ask, what exactly would you do with the interface other than implement it? Interfaces are typically only useful when you cast to it (ie does this class support 'IBar'), or have parameters or setters that take it (ie i take an 'IBar'). With ICloneable - we went through the entire Framework and failed to find a single usage anywhere that was something other than an implementation of it. We've also failed to find any usage in the 'real world' that also does something other than implement it (in the ~60,000 apps that we have access to).

Now if you would just like to enforce a pattern that you want your 'cloneable' objects to implement, that's a completely fine usage - and go ahead. You can also decide on exactly what "cloning" means to you (ie deep or shallow). However, in that case, there's no need for us (the BCL) to define it. We only define abstractions in the BCL when there is a need to exchange instances typed as that abstraction between unrelated libraries.

David Kean (BCL Team)

查看更多
姐就是有狂的资本
5楼-- · 2019-01-04 08:43

I think the question "why" is needless. There is a lot of interfaces/classes/etc... which is very usefull, but is not part of .NET Frameworku base library.

But, mainly you can do it yourself.

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

public abstract class CloneableBase<T> : ICloneable<T> where T : CloneableBase<T> {
    public abstract T Clone();
    object ICloneable.Clone() { return this.Clone(); }
}

public abstract class CloneableExBase<T> : CloneableBase<T> where T : CloneableExBase<T> {
    protected abstract T CreateClone();
    protected abstract void FillClone( T clone );
    public override T Clone() {
        T clone = this.CreateClone();
        if ( object.ReferenceEquals( clone, null ) ) { throw new NullReferenceException( "Clone was not created." ); }
        return clone
    }
}

public abstract class PersonBase<T> : CloneableExBase<T> where T : PersonBase<T> {
    public string Name { get; set; }

    protected override void FillClone( T clone ) {
        clone.Name = this.Name;
    }
}

public sealed class Person : PersonBase<Person> {
    protected override Person CreateClone() { return new Person(); }
}

public abstract class EmployeeBase<T> : PersonBase<T> where T : EmployeeBase<T> {
    public string Department { get; set; }

    protected override void FillClone( T clone ) {
        base.FillClone( clone );
        clone.Department = this.Department;
    }
}

public sealed class Employee : EmployeeBase<Employee> {
    protected override Employee CreateClone() { return new Employee(); }
}
查看更多
贪生不怕死
6楼-- · 2019-01-04 08:43

Having read recently the article Why Copying an Object is a terrible thing to do?, I think this question needs additional clafirication. Other answers here provide good advices, but still the answer isn't complete - why no ICloneable<T>?

  1. Usage

    So, you have a class that implements it. While previously you had a method that wanted ICloneable, it now has to be generic to accept ICloneable<T>. You would need to edit it.

    Then, you could have got a method that checks if an object is ICloneable. What now? You can't do is ICloneable<> and as you don't know the type of the object at compile-type, you can't make the method generic. First real problem.

    So you need to have both ICloneable<T> and ICloneable, the former implementing the latter. Thus an implementer would need to implement both methods - object Clone() and T Clone(). No, thanks, we already have enough fun with IEnumerable.

    As already pointed out, there is also the complexity of inheritance. While covariance may seem to solve this problem, a derived type needs to implement ICloneable<T> of its own type, but there is already a method with the same signature (= parameters, basically) - the Clone() of the base class. Making your new clone method interface explicit is pointless, you will lose the advantage you sought when creating ICloneable<T>. So add the new keyword. But don't forget that you would also need to override the base class' Clone() (the implementation has to remain uniform for all derived classes, i.e. to return the same object from every clone method, so the base clone method has to be virtual)! But, unfortunately, you can't both override and new methods with the same signature. Choosing the first keyword, you'd lose the goal you wanted to have when adding ICloneable<T>. Chossing the second one, you'd break the interface itself, making methods that should do the same return different objects.

  2. Point

    You want ICloneable<T> for comfort, but comfort is not what interfaces are designed for, their meaning is (in general OOP) to unify the behavior of objects (although in C#, it is limited to unifying the outer behavior, e.g. the methods and properties, not their workings).

    If the first reason hasn't convinced you yet, you could object that ICloneable<T> could also work restrictively, to limit the type returned from the clone method. However, nasty programmer can implement ICloneable<T> where T is not the type that is implementing it. So, to achieve your restriction, you can add a nice constraint to the generic parameter:
    public interface ICloneable<T> : ICloneable where T : ICloneable<T>
    Certainly more restrictive that the one without where, you still can't restrict that T is the type that is implementing the interface (you can derive from ICloneable<T> of different type that implements it).

    You see, even this purpose couldn't be achieved (the original ICloneable also fails at this, no interface can truly limit the behavior of the implementing class).

As you can see, this proves making the generic interface is both hard to fully implement and also really unneeded and useless.

But back to the question, what you really seek is to have comfort when cloning an object. There are two ways to do it:

Additional methods

public class Base : ICloneable
{
    public Base Clone()
    {
        return this.CloneImpl() as Base;
    }

    object ICloneable.Clone()
    {
        return this.CloneImpl();
    }

    protected virtual object CloneImpl()
    {
        return new Base();
    }
}

public class Derived : Base
{
    public new Derived Clone()
    {
        return this.CloneImpl() as Derived;
    }

    protected override object CloneImpl()
    {
        return new Derived();
    }
}

This solution provides both comfort and intended behavior to users, but it's also too long to implement. If we didn't want to have the "comfortable" method returning the current type, it is much more easy to have just public virtual object Clone().

So let's see the "ultimate" solution - what in C# is really intented to give us comfort?

Extension methods!

public class Base : ICloneable
{
    public virtual object Clone()
    {
        return new Base();
    }
}

public class Derived : Base
{
    public override object Clone()
    {
        return new Derived();
    }
}

public static T Copy<T>(this T obj) where T : class, ICloneable
{
    return obj.Clone() as T;
}

It's named Copy not to collide with the current Clone methods (compiler prefers the type's own declared methods over extension ones). The class constraint is there for speed (doesn't require null check etc.).

I hope this clarifies the reason why not to make ICloneable<T>. However, it is recommended not to implement ICloneable at all.

查看更多
叛逆
7楼-- · 2019-01-04 08:54

It's pretty easy to write the interface yourself if you need it:

public interface ICloneable<T> : ICloneable
        where T : ICloneable<T>
{
    new T Clone();
}
查看更多
登录 后发表回答