【问题标题】:How to workaround missing ICloneable interface when porting .NET library to PCL?将 .NET 库移植到 PCL 时如何解决缺少 ICloneable 接口的问题?
【发布时间】:2013-12-04 13:18:51
【问题描述】:

我正在将现有的 .NET 类库移植到可移植类库。 .NET 库广泛使用了ICloneable 接口,该接口不包含在可移植子集中。

通常,我在 .NET 类库中会遇到这样的类定义:

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

如何才能成功将此代码移植到可移植类库?我是否必须重写 Clone 方法调用,还是有不那么侵入性的解决方法?

我不能简单地删除泛型类型约束where T : ICloneable,因为那样Bar 方法将无法编译。

我可以编写一个替换接口来代替移植代码中的ICloneable

public interface IPCLCloneable {
    object Clone();
}

只要我只使用实现 IPCLCloneable 的类来实例化 Foo&lt;T&gt;,它就可以工作,但它不会工作,例如来自实现 ICloneable 的核心库中的类型,例如如Array:

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

(为了完整起见,应该指出ICloneable接口在可移植子集核心库中没有显式实现,因为它不存在,但object Clone()方法确实存在于可移植子集Array类中,因此对于数组实现,例如int[]。)

我还有什么其他选择?

【问题讨论】:

  • 这是个好问题。但是,您是否不必编写一个“新的” int[] 类来实现它的 Clone 方法?或者 PCL 是否有 int[].Clone() 没有 实现 ICloneable?我还可以想象您可以删除约束,并首先尝试 ICloneable ,并有一个替代实现作为后备。不是很漂亮,它会消除约束的一个好处(编译时类型检查),但这很可能是你必须付出的代价......
  • @Luaan 是的,int[].Clone() 在 PCL 中可用,只是ICloneable 接口不存在。并感谢您的建议。当然,“越漂亮”越好,但最终我可能不得不做一些重大的重构......
  • 嗯,这很烦人。似乎您可能必须使用反射,或者为所有已知的原始类型编写包装器 - 而不是使用 int[] 作为泛型类型,您必须传递 Cloneable。考虑一下,使用包装器可能是最好的方法 - 它仍然可以为您提供编译时类型检查,而且开销很小......
  • 是的,我也考虑过包装器。谢谢@Luaan 的建议。
  • @qujck 如果我在 .NET 应用程序中使用 PCL,那将不起作用,因为那样我会有重复的接口。这也无助于与int[].Clone() 和类似情况相关的场景。

标签: c# generics interface portable-class-library icloneable


【解决方案1】:

ICloneable 是我们在设计 API 时犯错误的教科书示例。是做深拷贝还是浅拷贝是未定义的,框架设计指南现在推荐not using it

也就是说,如果您仍然需要使用它,您可能可以通过在 PCL 中定义接口(具有完全相同的名称)从 PCL 中执行此操作,并且当您在确实具有 ICloneable 的平台上运行时,替换具有 ICloneable 定义的程序集与具有向前“真实”版本的类型的程序集。请在此处查看我的答案以了解更多信息:Is there any way I can implement IValidatableObject on Portable Class Library Project?

【讨论】:

  • 非常感谢@Daniel,类型转发似乎也是一种值得进一步探索的方法。但是,如果我错了,请纠正我,但是您提出的方法不会立即解决 Foo&lt;int[]&gt; 的问题,对吧?
  • @AndersGustafsson 你说得对,Foo&lt;int[]&gt; 解决不了编译时问题
  • 我认为使用Func&lt;T, T&gt; 将满足使用依赖注入。我将参数命名为clone,并附有说明它应该克隆给定参数的文档。接口也不保证克隆的确切类型,因此这种方法更可取。
【解决方案2】:

我不知道这是否可以接受:

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

【讨论】:

  • 好点,@JeppeStigNielsen,我为什么没有想到扩展方法? :-) 我会考虑一下,看看它是否是我的端口的有效解决方案。
【解决方案3】:

.NET 比较器基础结构也有类似的问题。您有时想要对未实现 IComparable 的对象进行排序,但您可以在外部为排序算法指定 IComparer&lt;T&gt;。你可以在这里做类似的事情:创建一个接口ICloneProvider&lt;T&gt;,它支持克隆给定类型的对象。您必须确保所有需要克隆的代码都有该接口的合适实例可用。

【讨论】:

  • 我喜欢这个主意,但我不确定 OP 是否可以有效地做到这一点。它需要对架构(至少是部分)进行一些重大的重写,并且 OP 说他正在做一个移植,所以他手上可能有很多代码要转换。
  • 谢谢@usr,这也是一个好点。我将评估这是否有效地适用于我的端口。
  • @AndersGustafsson 看看 EqualityComparer.Default 是如何工作的。它尝试使用不同的策略创建一个合理的默认比较器。您也可以这样做:如果公共克隆方法可用,请使用该方法。如果它实现了 IPclCloneable,请使用它。如果类型名称是 SomeClass 并且有另一个名为 SomeClassCloneProvider 的类,请使用它。否则,失败。这将使您能够实现很多自动化。
【解决方案4】:

从 .NET Standard 2.0 版本开始,ICloneable 接口又回来了,所有平台都应该支持:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多