【问题标题】:Is this property a reference or a copy and how it would behave in memory?这个属性是引用还是副本,它在内存中的表现如何?
【发布时间】:2018-10-04 04:06:50
【问题描述】:

假设我有一本 Something 的字典:

private Dictionary<uint, Something> _somethingList = new Dictionary<uint, Something>();

Something 是,例如:

class Something
{
    public uint Id { get; set; }
    public string Name { get; set; }
    // ... more stuff here
}

然后我有Another 类:

class Another
{
    public Another(uint id, string name, Something connection)
    {
        Id = id;
        Name = name;
        Connection = connection;
    }

    public uint Id { get; set; }
    public string Name { get; set; }
    public Something Connection { get; set; }
}

并像这样使用它:

var test = new Another(1, "Test", _somethingList[1]);

Connection 将是 _somethingList 字典中键为 1 的值的引用或副本,还是会为其创建新内存?

我想我要问的是,它在内存中的表现如何,例如,它是否会复制内存中的属性数据,还是会像指向实际 _somethingList[1] 的指针一样?或者我如何自己验证这一点(我的意思是,如果我断点,是否有东西表明它是副本还是参考还是 w/e)?

【问题讨论】:

  • 这将是参考,不会创建新的内存。如果您将在其他地方更改_somethingList[1] 的成员。它也会在test 对象的Connection 中更改。
  • @Amit 感谢您的评论,所以Connection 就像是指向字典实际值的指针?
  • 是的。 Connection 只会携带 _somethingList[1] 的引用。它不会保存实际数据。
  • @Amit 好的,谢谢,我担心它可能是在复制它而不是引用它,我想知道是否有办法通过中断指向它来识别它
  • @Guapo 在 Visual Studio 中,右键单击本地窗口并选择 "Make Object ID",它将为特定对象分配一个唯一的整数。然后你可以区分两个引用是否指向完全相同的对象。

标签: c# pointers memory reference copy


【解决方案1】:

使用您的数据结构和类设计,如果您执行以下代码行。您会知道,在Another 类的属性Connection 中,您实际上拥有的是_somethingList[1] 的引用而不是副本。

使用分析器结果编辑我的答案,以便您也了解内存使用情况

更改了某些类(这只是为了扩大内存使用量,您不必在实际代码中考虑它。

public class Something
{
    public uint Id { get; set; }
    public string Name { get; set; }

    public List<string> stringList { get; set; }

    public Something (uint id, string name)
    {
        Id = id;
        Name = name;

        stringList = new List<string>();
        for(long i = 0; i < 10000; i++)
        {
            stringList.Add(i.ToString());
        }
    }

}

逻辑是,

        DateTime nowDateTime = DateTime.Now;
        Dictionary<uint, Something> _somethingList = new Dictionary<uint, Something>();

        Console.WriteLine((DateTime.Now - nowDateTime).TotalSeconds.ToString() + " Creating List _somethingList");

            //slowly creating lsit so you can get memory usage incresing in profiler
        for (uint i = 1; i < 10; i++)
        {
            _somethingList.Add(i, new Something(i, "amit" + i.ToString()));
            System.Threading.Thread.Sleep(1000);
        }

        Console.WriteLine((DateTime.Now - nowDateTime).TotalSeconds.ToString() +  " Before creating test object");
        System.Threading.Thread.Sleep(5 * 1000);
        Another test = new Another(1, "Test", _somethingList[1]);
        Console.WriteLine((DateTime.Now - nowDateTime).TotalSeconds.ToString() + "  After creating test object");

        Console.WriteLine((DateTime.Now - nowDateTime).TotalSeconds.ToString() + "  Name before editing:" + test.Connection.Name);
        _somethingList[1].Name = "EditedName";
        Console.WriteLine((DateTime.Now - nowDateTime).TotalSeconds.ToString() + "  Name after editing:" + test.Connection.Name);

输出是,

您可以看到,在创建somethingList 的每个对象时,分析器观察中的内存使用量显着增加,

但是从第二个 9 到 14,当我们将 _somethingList[1] 的引用设置为 Connection 时,分析器结果中的内存没有增加。这意味着它只是参考设置没有内存分配。

【讨论】:

  • +1 感谢您的详细回答,它确实向我保证了记忆力很好,但我真正想要的是迈克在 cmets 上发布的内容,它向我展示了如何追溯到它的起源甚至让我比较哪个很酷。不过,感谢您让我放心。
【解决方案2】:

.net 中的所有类都是引用类型,因此您将引用同一个字典条目,而不是它的副本。如果您正在寻找副本,请使用 scruct 而不是类。结构是值类型,但与类相比,其范围更小,行为更受限制。

如果您实际上需要按值复制并保留类,那么您可能可以从一些深度克隆方法中受益,并在传递给构造函数之前克隆您的对象。那里有许多深克隆和浅克隆的实现,你可以很容易地在网上找到一个。

【讨论】:

    【解决方案3】:

    有 2 种基本类型:引用和值。 Dictionary 并不重要,因为您从 Dictionary 中提取一个值并传递它。

    _somethingList[1] => 返回Something 对象,然后将其传递给Another.ctor

    把它想象成:

    Something somethingObj = _somethingList[1];
    var test = new Another(1, "Test", somethingObj);
    

    这些对象在内存中的行为方式有点棘手。您正在使用托管语言工作,这意味着您无法直接控制内存或优化。

    您可以检查身份平等。身份意味着位置(位置松散地是具有类型的内存地址)是相同的。如果两个对象具有相同的标识,那么它们也总是相等的。相等意味着存储在变量中的值是相同的,但变量本身可能存储也可能不存储在同一位置。

    可以使用Object.ReferenceEquals 检查身份。 可以使用Object.Equals 检查是否相等。

    在此示例中,我将展示如何根据 Something 检查身份和相等性。

    void Main()
    {   
        var a = new Something();
        var b = a;
        var c = new Something();
    
        Object.Equals(a,b);//True
        Object.Equals(b,c);//False
        Object.Equals(a,c);//False
        Object.ReferenceEquals(a,b);//True
        Object.ReferenceEquals(b,c);//False
        Object.ReferenceEquals(a,c);//False
    
        var d = M(a);//Pass a to M and check the identity of what comes back
    
        Object.ReferenceEquals(a, d);//True
    }
    
    public static Something M(Test e)
    {
        e.SomeVariable = 10;//a and e now refer to the same object location
        //e = new Test();//a and e refer to different objects when you do this.  This will not be reflected back to the caller unless you use ref parameters
        return e;
    }
    
    public class Something { public int SomeVariable {get;set;} }
    

    同样,在处理优化和ref parameters 时,事情变得很棘手。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-20
      • 2011-02-10
      • 1970-01-01
      • 2014-03-02
      • 2023-03-10
      • 1970-01-01
      • 2012-10-03
      • 2011-11-15
      相关资源
      最近更新 更多