15

I am porting an existing .NET class library to a Portable Class Library. The .NET library makes extensive use of the ICloneable interface, which is not included in the portable subset.

Typically, I am faced with class definitions like this in the .NET class library:

public class Foo<T> where T : ICloneable
{
    public void Bar(T item) {
        var x = item.Clone();
        ...
    }
}

What can I do to successfully port this code to a Portable Class Library? Do I have to rewrite the Clone method invocations, or is there a less intrusive workaround?

I cannot simply remove the generic type constraint where T : ICloneable because then the Bar method will not compile.

I could write up a replacement interface to use instead of ICloneable in the ported code:

public interface IPCLCloneable {
    object Clone();
}

This would work as long as I only instantiate Foo<T> with classes that implement IPCLCloneable, but it will not work for example with types from the core libraries that implement ICloneable, such as Array:

var x = new Foo<int[]>();  // Compilation error in PCL library

(For completeness, it should be pointed out that the ICloneable interface is not explicitly implemented in the portable subset core libraries since it does not exist, but the object Clone() method does exist in the portable subset Array class, and consequentially for array implementations such as int[].)

What other options do I have?

Anders Gustafsson
  • 15,837
  • 8
  • 56
  • 114
  • That's a great question. However, wouldn't you have to write a "new" int[] class anyway, to implement its Clone method? Or does PCL have int[].Clone() *without* implementing ICloneable? I can also imagine you could remove the constraint, and try ICloneable first, and have an alternate implementation as a fallback. Not very pretty, and it would remove one benefit of constraints (compile-time type checking), but that might very well be the price you have to pay... – Luaan Dec 04 '13 at 13:35
  • @Luaan Yes, `int[].Clone()` is available in PCL, it is only the `ICloneable` interface that isn't there. And thanks for the suggestions. The "prettier" the better, of course, but in the end I might have to do some major refactoring after all... – Anders Gustafsson Dec 04 '13 at 13:40
  • 1
    Well, that's mighty annoying. It seems like you might to have to use reflection, or write wrappers for all the known primitive types - instead of using int[] as the generic type, you'll have to pass Cloneable. Thinking about it, using the wrapper is probably the best way - it still gives you compilation-time type checking, and it has very little overhead... – Luaan Dec 04 '13 at 13:43
  • Yes, I have considered wrappers, too. Thanks, @Luaan, for the suggestion. – Anders Gustafsson Dec 04 '13 at 13:46
  • Why not define `ICloneable` yourself? – qujck Dec 04 '13 at 13:50
  • 1
    @qujck That would not work if I used the PCL in a .NET application, because then I would have duplicate interfaces. It would also not help up the scenario related to `int[].Clone()` and similar. – Anders Gustafsson Dec 04 '13 at 13:52
  • PCL stripped types and methods for two reasons. One is the obvious one, not supported on one of the platforms. The other was *cleanup*, getting rid of stuff that shouldn't be there anymore. ICloneable fits that category, it barely escaped getting deprecated in the full version of .NET. It is a *very* flawed interface, it isn't explicit about shallow or deep cloning. Just don't use it. If you have to then just create a better version. – Hans Passant Dec 04 '13 at 18:10
  • @HansPassant I am fully aware of the shortcomings of the `ICloneable` interface, and in theory I agree that usage should be avoided. In practice however ... it will be quite a lot of work to replace it in the third party library I am trying to port. Hence the request for efficient workarounds. – Anders Gustafsson Dec 04 '13 at 18:38

4 Answers4

13

ICloneable is kind of the textbook example of where we made a mistake when designing an API. Whether it does a deep or shallow copy is undefined, and the Framework Design Guidelines now recommend not using it.

That said, if you need to use it anyway, you can probably do so from PCLs by defining the interface (with the exact same name) in a PCL, and when you are running on a platform that does have ICloneable, replacing the assembly with the ICloneable definition with one which has a type forward to the "real" version. See my answer here for a bit more on this: Is there any way I can implement IValidatableObject on Portable Class Library Project?

Community
  • 1
  • 1
Daniel Plaisted
  • 16,674
  • 4
  • 44
  • 56
  • Many thanks, @Daniel, type forwarding also seems like an approach worth exploring further. However, correct me if I am wrong, but your proposed approach will not immediately solve the issue with `Foo`, right? – Anders Gustafsson Dec 04 '13 at 21:58
  • @AndersGustafsson You're right, it won't solve the compile-time issue with `Foo` – Daniel Plaisted Dec 04 '13 at 23:19
  • I think using `Func` will satisfy using dependency injection. i named the parameter `clone` with documentation that states it should clone given argument. Interfaces don't guarantee exact type of cloning either so this approach is preferable. – M.kazem Akhgary Sep 03 '17 at 05:59
4

I don't know if this is acceptable:

public class Foo<T>
{
  // since we can't check T at compile-time anymore (no constraint), we do this
  static Foo()
  {
    if (!typeof(IPclCloneable).IsAssignableFrom(typeof(T)) && !typeof(T).IsArray)
      throw new ArgumentException("Type must have Clone method", "T");
  }

  public void Bar(T item)
  {
      var x = item.Clone();
      ...
  }
}

public static class YourExtensions
{
  public static object Clone(this object obj)
  {
    if (obj == null)
      throw new ArgumentNullException("obj");
    var objIPclCloneable = obj as IPclCloneable;
    if (objIPclCloneable != null)
      return objIPclCloneable.Clone();
    var objArray = obj as Array;
    if (objArray != null)
      return objArray.Clone();

    throw new ArgumentException("Type of 'this' must have Clone method", "obj");
  }
}

public interface IPclCloneable
{
  object Clone();
}
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • Good point, @JeppeStigNielsen, why didn't I think of extension methods? :-) I'll give this some thought and see whether it is an efficient solution for my port. – Anders Gustafsson Dec 04 '13 at 13:48
3

The .NET comparer infrastructure has a similar problem. You sometimes want to sort objects that do not implement IComparable but you can externally specify an IComparer<T> to the sort algorithm. You could do a similar thing here: Create an interface ICloneProvider<T> that supports cloning an object of the given type. You must make sure that all code that needs to clone something has a suitable instance of that interface available.

usr
  • 168,620
  • 35
  • 240
  • 369
  • I like that idea, but I'm not sure if the OP can do this efficiently. It needs some major rewrites of (at least some part of) the architecture, and OP said he's doing a port, so he's probably got a lot of code to convert on his hands. – Luaan Dec 04 '13 at 13:49
  • Thanks, @usr, also a good point. I'll evaluate whether this is efficiently applicable to my port. – Anders Gustafsson Dec 04 '13 at 13:49
  • 3
    @AndersGustafsson look at how EqualityComparer.Default works. It tries to create a sensible default comparer using different strategies. You could do the same: if a public Clone method is available, use that. If it implement IPclCloneable, use that. If the type name is SomeClass and there is another class called SomeClassCloneProvider, use that. Otherwise, fail. This would allow you to automate a lot. – usr Dec 04 '13 at 13:55
1

As of .NET Standard 2.0 release, the ICloneable interface is back and should be supported by all plateforms:

https://learn.microsoft.com/en-us/dotnet/api/system.icloneable?view=netstandard-2.0

Moslem Ben Dhaou
  • 6,897
  • 8
  • 62
  • 93