.NET中C#堆VS栈:Part III

本文通过一个具体的示例,深入探讨了在C#中值类型和引用类型的拷贝机制,展示了如何利用ICloneable接口实现深拷贝,避免引用类型拷贝时可能出现的错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

所谓复制

为了更好的说明问题,我们来对比一下堆上的值类型与引用类型。首先看一下值类型,下面的类和结构体,有一个Dude类包含Name和两个shoe属性,一个CopyDude()方法用来创建新的Dude实例。

public struct Shoe{
    public string Color;
}

public class Dude
{
    public string Name;
    public Shoe RightShoe;
    public Shoe LeftShoe;

    public Dude CopyDude()
    {
        Dude newPerson = new Dude();
        newPerson.Name = Name;
        newPerson.LeftShoe = LeftShoe;
        newPerson.RightShoe = RightShoe;

        return newPerson;
    }

    public override string ToString()
    {
        return (Name + " : Dude!, I have a " + RightShoe.Color  +
            " shoe on my right foot, and a " +
            LeftShoe.Color + " on my left foot.");
    }

}

因为Shoe是Dude类的一个属性,所以它们都在堆中。

像下面这样运行

 

public static void Main()
{
    Class1 pgm = new Class1();

    Dude Bill = new Dude();
    Bill.Name = "Bill";
    Bill.LeftShoe = new Shoe();
    Bill.RightShoe = new Shoe();
    Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";

    Dude Ted =  Bill.CopyDude();
    Ted.Name = "Ted";
    Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";

    Console.WriteLine(Bill.ToString());
    Console.WriteLine(Ted.ToString());            

}

输出和预想的一样

 

Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left foot.

Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot.

 

如果我们把Shoe变成引用类型会怎么样?

 

public class Shoe{
    public string Color;
}

仍然像刚才那样调用main()方法

 

Bill : Dude!, I have a Red shoe on my right foot, and a Red on my left foot

Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot

 

这是一个很明显的错误。它是怎么产生的?下面是堆中的情形

因为我们把Shoe从值类型改成了引用类型,当引用类型的内容被copy时只是copy指向它的指针(而不是指针指向的真实的object),我们需要做一些额外的工作来让引用类型表现的像一个值类型。

很庆幸,有一个接口可以帮助我们完成这个:ICloneable。这个接口约定了所有的Dude要接受并且定义如何复制引用类型,来避免上面的问题。所有需要被复制的类都需要使用这个接口,包括Shoe class。

ICloneable 包含一个方法: Clone()

 

public object Clone()
{
}

下面是它在Shoe类中的实现

public class Shoe : ICloneable
{
    public string Color;

    public object Clone()
    {
        Shoe newShoe = new Shoe();
        newShoe.Color = Color.Clone() as string;
        return newShoe;
    }
}

在Clone()方法内部,创建了一个新的Shoe,复制所有的引用类型和值类型并且返回这个新实例。你可能注意到了,string类已经实现了ICloneable接口,所以我们可以调用Color.Clone()。因为Clone()返回一个object类型的引用,我们在使用前需要转换一下类型。

下面在CopyDude()方法中,我们要用clone替代原来的方法。

public Dude CopyDude()
{
    Dude newPerson = new Dude();
    newPerson.Name = Name;
    newPerson.LeftShoe = LeftShoe.Clone() as Shoe;
    newPerson.RightShoe = RightShoe.Clone() as Shoe;

    return newPerson;
}

现在,我们在执行main:

public static void Main()
{
    Class1 pgm = new Class1();

    Dude Bill = new Dude();
    Bill.Name = "Bill";
    Bill.LeftShoe = new Shoe();
    Bill.RightShoe = new Shoe();
    Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";

    Dude Ted =  Bill.CopyDude();
    Ted.Name = "Ted";
    Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";

    Console.WriteLine(Bill.ToString());
    Console.WriteLine(Ted.ToString());            

}

得到结果:

Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left foot

Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot

使我们想要的

封装

 

作为一个常规练习,我们总是要clone引用类型和复制类型。下面我们更进一步,直接将Dude类实现ICloneable来替代CopyDude()方法。

 

public class Dude: ICloneable
{
    public string Name;
    public Shoe RightShoe;
    public Shoe LeftShoe;

    public override string ToString()
    {
    return (Name + " : Dude!, I have a " + RightShoe.Color  +
        " shoe on my right foot, and a " +
        LeftShoe.Color + " on my left foot.");
    }
    #region ICloneable Members
    public object Clone()
    {
        Dude newPerson = new Dude();
        newPerson.Name = Name.Clone() as string;
        newPerson.LeftShoe = LeftShoe.Clone() as Shoe;
        newPerson.RightShoe = RightShoe.Clone() as Shoe;

        return newPerson;
    }
    #endregion
}

main()方法也相应的改一下,使用Dude.Clone()

public static void Main()
{
    Class1 pgm = new Class1();

    Dude Bill = new Dude();
    Bill.Name = "Bill";
    Bill.LeftShoe = new Shoe();
    Bill.RightShoe = new Shoe();
    Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue";

    Dude Ted =  Bill.Clone() as Dude;
    Ted.Name = "Ted";
    Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";

    Console.WriteLine(Bill.ToString());
    Console.WriteLine(Ted.ToString());            
}

最终的显示结果:

Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left foot.

Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot.

一些有意思的事情需要注意,等号操作符对System.String做了clone操作所以不必担心string类型的复制问题。但是需要注意内存膨胀。string作为引用类型确实是一个指针指向堆中的一块地方,只是单纯的表现的像是值类型。

 

原文链接:

https://www.c-sharpcorner.com/article/C-Sharp-heaping-vs-stacking-in-net-part-iii/

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值