Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# - An objects scope

Tags:

c#

I have looked at this and this and I have the following question to see if I understand correctly. Given the code

using System;

namespace returnObject
{
    class myObject
    {
        public int number { get; set; }

    }

    class Program
    {
        static void Main(string[] args)
        {

            myObject mainObj = make();
            mainObj.number = 7;
        }

        public static myObject make()
        {
            myObject localObj = new myObject();
            localObj.number = 4;
            return localObj;
        }

    }
}

I would expect localObj to go out of scope at the end of the make method and, therefore, the setting of obj.number to 7 in the main function to fail. It doesn't. I think I am correct in stating that:

  • localObj is a reference to an object
  • localObj is created on the stack
  • localObj goes out of scope at the end of the make method.
  • the object that localObj refers to is on the heap.

So, am I right in thinking that ordinarily the object that localObj refers to would have been marked for deletion by the garbage collector at the end of the make method but since the reference value has been passed back to mainObj, the object is referenced and therefore not eligible for deletion?

In addition is object creation in this way considered good practice?

like image 456
Paul Coombes Avatar asked Apr 03 '11 14:04

Paul Coombes


1 Answers

Am I right in thinking that ordinarily the object that localObj refers to would have been marked for deletion by the garbage collector at the end of the make method but since the reference value has been passed back to mainObj, the object is referenced and therefore not eligible for deletion?

That is an extremely complex question, and yet it is of a form that admits only a yes or no answer. Rather than trying to answer that, let me break it down into points following on your original points.

localObj is a reference to an object

Better: localObj is a local variable. A local variable is a storage location of reference type. The storage location contains a reference to an object, or null.

localObj is created on the stack

Correct, though this is an implementation detail. The storage location associated with the variable is allocated from the temporary pool. As an implementation detail, the CLR uses the call stack as a temporary pool. It need not; the stack is just a cheap, convenient way to get a temporary pool.

localObj goes out of scope at the end of the make method.

Correct. The scope of a variable is defined as the region of program text in which it may be used via its unqualified name. The scope of this particular variable is the entire body of the method. (I note also that in C# it is traditional to begin methods, properties and types with a capital letter, as you have not done.)

the object that localObj refers to is on the heap.

Correct. As an implementation detail, all references are either null, or refer to an object in the long-term storage, aka, the managed heap. An implementation of the CLR could use flow analysis to determine that a particular object does not escape the lifetime of the local variable which hold the sole reference to it, and therefore allocate it on the temporary pool. In practice, this does not happen in any implementation I'm aware of.

had it not been returned, the object that localObj refers to would have been marked for deletion by the garbage collector at the end of the make method

No. Here is your first major mistaken impression. The GC is not deterministic. It does not see immediately that a local variable has gone out of scope and therefore an object has been orphaned. The object lives on until some policy triggers a garbage collection. And even then, the object may have survived a previous collection and been promoted to a later generation. Later generations are collected less frequently. All you know is that the object will no longer be marked for survival. Remember, a mark-n-sweep garbage collector marks objects for survival, not for deletion.

Furthermore, it is perfectly legal for an object to be cleaned up by the GC before the make method ends. This is a common cause of bugs when dealing with managed/unmanaged code interop. Consider the following scenario:

void M() 
{
    FileThingy f = GetFileThingy();
    MyUnmanagedLibrary.ConsumeFileThingy(f);
}

When is f eligable for collection? As soon as the GC can determine that no managed code ever consumes the reference again. But no managed code ever consumes this reference the instant after the unmanaged code gets its copy of the reference, and therefore the GC is within its rights to collect the object on another thread immediately after the call is invoked but before the managed code runs. To solve this problem you need to use a KeepAlive to keep the object alive.

You cannot rely on the GC reclaiming storage at precisely the moment when a local variable goes out of scope. The lifetime of the object is highly likely to be longer than the lifetime of the variable, and it is legal for it to be shorter if the GC can prove that managed code can't tell the difference.

since the reference value has been passed back to mainObj, the object is referenced and therefore not eligible for deletion

Correct. But again, suppose the mainObj routine then did not use the object passed back for anything. The jitter is within its rights to notice that fact and optimize away the tramping around of the unused data, and thereby make the object eligable for early collection.

What I'm getting at here is the garbage collector should be thought of as smarter than you are about managing your memory. The object will go away no sooner than it needs to, and probably later than it could, but possibly earlier than you'd think. Stop worrying and learn to love the uncertainty; the GC is managing things for you and it does a great job.

like image 85
Eric Lippert Avatar answered Oct 06 '22 08:10

Eric Lippert