结构体或深拷贝 - C#

6
我终于成功地复制了我的对象的值类型,这个类使用字典来存储动态属性。不过我有两个疑问,一个是mono兼容性,另一个是效率问题。我刚开始学习C#,对编程还有很多需要学习的地方,如果我使用了一些错误的术语,请见谅: P
我使用了这种方法来复制我的对象:如何在.NET(特别是C#)中深度复制对象? ... 来复制我的对象。 我将会有数百个这样的对象,所以我想知道用这种方式复制它们是否非常低效?结构体会是更好的选择吗?然而,我不确定什么时候该使用结构体。并且它能够在mono上移植吗?一些搜索结果表明,这种序列化可能会引起问题。

3
这是.NET框架中最显著的不足之一。它应该完全支持内存复制克隆以进行对象深度克隆,就像Java通过将对象标记为可克隆来让你可以克隆任何对象一样。 - Chris Marisic
1
一些关于为什么需要深拷贝的解释会很有帮助。 - Adam Robinson
@Adam Robinson,我指的是物理上包含整个对象的整个内存块,而不是指针的内存地址...如果你认为不需要进行深度复制,那显然你从未编写过需要克隆列表并在不影响原始列表的情况下对其进行修改的深层列表代码... - Chris Marisic
4
除了浅复制意义上的情况外,没有一个“块”包含一个“整个”对象。 - Will Dean
1
@Marked,如果你是C#的新手,我强烈推荐阅读Jeffrey Richter的CLR via C#(http://www.amazon.com/CLR-via-C-Jeffrey-Richter/dp/0735627045)。他在另一篇文章中使用了通过序列化实现深拷贝的方法。 - Jason
显示剩余6条评论
2个回答

3

根据评论中的跟进回复,你所寻找的最佳解决方案是最简单的:自己编写代码来复制对象。

如果您的类真的只是一个包装字典的简单类,用于存储自定义属性的键/值对,那么您可以使用Dictionary(IDictionary)构造函数将一个字典的值复制到另一个字典中。

class MyWrapper
{
    private Dictionary<string, object> properties = 
                                         new Dictionary<string, object>();

    public MyWrapper Clone()
    {
        MyWrapper output = new MyWrapper();

        output.properties = new Dictionary<string, object>(properties);
    }
}

显然,这是一个简化的类,实际上并没有做任何事情,但根据你所描述的,这应该能满足你的需求。没有反射,没有“陷阱”,只是从一个字典中简单地复制值到另一个字典中。
编辑
我无法确定它在mono上的可移植性,因为我只是一个Windows开发者,但就效率而言,显式解决方案,其中您复制所需内容,将比基于反射的解决方案更胜一筹。
在基于引用的面向对象语言中,任意类型的真正深度复制的概念不是易于(甚至不安全地)实现的。虽然对于简单的类来说很容易复制,但引用循环、没有无参构造函数的类和不可变类型都存在挑战。
例如,请考虑以下类:
public class Foo
{
    public Foo Next { get; set; }
}

这是一个最简单的单向链表实现。一个朴素的深度复制算法会从第一个 Foo 实例开始,然后通过递归地沿着 Next 引用链克隆它,直到遇到一个 null 值。然而,这样做不仅会消耗内存,还会得到不代表原始对象实际克隆的对象:

Foo first = new Foo();

first.Next = new Foo();

first.Next.Next = first;

这是完全合法的(甚至是合理的)操作,但现在我们有了一个循环引用的环,这将破坏我们天真的克隆算法。因此现在我们需要实现一个对象缓存。

Dictionary<object, object> clonedObjects;

现在,在克隆算法中,当给属性或字段赋值时,我们会检查缓存以查看我们即将复制的引用是否已经被克隆。如果是,则使用该值而不是克隆一个新值。这将为我们提供代表原始对象的全新对象图,并且也是完整的克隆。很棒,对吧?
现在,对于无参数的构造函数呢?这个问题甚至在完全通用的意义上都无法解决。如果我创建这个类:
public class Bar
{
    public Bar(string gotcha) { }
}

在一般意义下,无法克隆此类;因为您无法知道如何调用构造函数(虽然可以通过反射获得ConstructorInfo,但它的调用语义将完全不知道)。最好的方法是存储元数据(通过类上的自定义属性),以了解要调用哪个构造函数以及如何调用它(例如应该传递的字段列表顺序),但这需要先了解克隆机制,并且暗示构造函数的参数是原始对象上的字段,这并不一定是正确的。
现在我们又遇到了另一个问题:不可变引用类型。这也可能导致意外行为。不可变引用类型是指其(外部可见)值不能更改的引用类型;在大多数情况下,这些类被设计为表现出值类型语义。它们也经常缺乏无参构造函数(甚至可能根本没有公共可访问构造函数),从而使它们遭受我们之前的烦恼,但它们也可以使用基于工厂的方法,以便它们可以确保引用相等也意味着值相等,反之亦然(后一种情况较少见,但如果我们谈论的是完全天真的克隆机制,则必须涵盖它)。这又意味着另一个自定义属性,以指示克隆机制应该仅复制引用而不是克隆实际对象。
因此,简单的深度复制机制在处理任意类型时根本不可能。必须设计类型以考虑克隆机制,并且可能需要做出让步或以特定方式装饰自己以使其与之配合。这再加上相对不频繁的需求,很可能是为什么现在没有框架级深度复制机制的原因,以及为什么您应该考虑更明确的复制机制,以便您知道要复制什么(以及什么可能无关紧要),以便确保您得到的就是您想要的。

这似乎是一个不错的解决方案,但是我接受了deepClone方法,因为我已经实现了它并且它运行良好。此外,关于结构体还有更多信息。然而,没有人回答与效率或单一可移植性相关的问题 :/ - marked
@marked:请看我的编辑。很抱歉这段话有点长,但即使它不改变你的方法,也是你应该记在心里的事情。 - Adam Robinson
看起来我不得不重新考虑并采用你的解决方案,因为序列化不具备可移植性(至少需要大量工作)。你的解决方案简单而有效,运行良好,谢谢!详细的解释也受到赞赏! - marked

1

结构体在使用前应该仔细考虑一些独特的特性。

  • 它们都是值类型(这会增加传递它们的挑战)
  • 所有的内存都分配在栈上,而不是堆上
  • 它们必须有无参构造函数
  • 结构体不支持继承

http://msdn.microsoft.com/en-us/library/aa288471(v=vs.71).aspx

在询问结构体是否适合您的情况之前,请考虑这些要点。
通常,我会坚持为我的类编写一个创建深拷贝的方法。您可能可以使用泛型和反射的组合来编写一个相当抽象的DeepClone<T>(T object)方法。或者选择简单的方法,只需为所涉及的类编写一个定制的方法即可。

@Yuck 哈哈,我也不想这样做。我只是在指出一些选项。=P - Matthew Cox
@呸 很不幸,.NET没有一个坚实的机制来克隆对象。现在虽然我不是专家,但尝试创建一个可以克隆任何类的神奇方法似乎需要使用反射。例如,像属性这样的东西没有使用反射不能被克隆(据我所知)。不过我有时候也会错 =P - Matthew Cox
“使用堆栈而不是堆” - 我鼓励您阅读《关于值类型的真相》(http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx)。 - Anthony Pegram
1
@Anthony Pegram 我从MSDN发布的文章链接坦率地指出:“但是,当您实例化一个结构时,它会在堆栈上创建...” 如果这是您投票反对的原因。那么我鼓励您重新考虑。 - Matthew Cox
2
@Matthew:那篇文章过于简单化了,不幸的是。我建议阅读Eric Lippert的文章,这是@Anthony链接的。 - Adam Robinson
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接