Do you need to dispose of objects and set them to null?

前端 未结 11 1832
不思量自难忘°
不思量自难忘° 2020-11-22 10:58

Do you need to dispose of objects and set them to null, or will the garbage collector clean them up when they go out of scope?

相关标签:
11条回答
  • 2020-11-22 11:42

    Normally, there's no need to set fields to null. I'd always recommend disposing unmanaged resources however.

    From experience I'd also advise you to do the following:

    • Unsubscribe from events if you no longer need them.
    • Set any field holding a delegate or an expression to null if it's no longer needed.

    I've come across some very hard to find issues that were the direct result of not following the advice above.

    A good place to do this is in Dispose(), but sooner is usually better.

    In general, if a reference exists to an object the garbage collector (GC) may take a couple of generations longer to figure out that an object is no longer in use. All the while the object remains in memory.

    That may not be a problem until you find that your app is using a lot more memory than you'd expect. When that happens, hook up a memory profiler to see what objects are not being cleaned up. Setting fields referencing other objects to null and clearing collections on disposal can really help the GC figure out what objects it can remove from memory. The GC will reclaim the used memory faster making your app a lot less memory hungry and faster.

    0 讨论(0)
  • 2020-11-22 11:44

    Objects never go out of scope in C# as they do in C++. They are dealt with by the Garbage Collector automatically when they are not used anymore. This is a more complicated approach than C++ where the scope of a variable is entirely deterministic. CLR garbage collector actively goes through all objects that have been created and works out if they are being used.

    An object can go "out of scope" in one function but if its value is returned, then GC would look at whether or not the calling function holds onto the return value.

    Setting object references to null is unnecessary as garbage collection works by working out which objects are being referenced by other objects.

    In practice, you don't have to worry about destruction, it just works and it's great :)

    Dispose must be called on all objects that implement IDisposable when you are finished working with them. Normally you would use a using block with those objects like so:

    using (var ms = new MemoryStream()) {
      //...
    }
    

    EDIT On variable scope. Craig has asked whether the variable scope has any effect on the object lifetime. To properly explain that aspect of CLR, I'll need to explain a few concepts from C++ and C#.

    Actual variable scope

    In both languages the variable can only be used in the same scope as it was defined - class, function or a statement block enclosed by braces. The subtle difference, however, is that in C#, variables cannot be redefined in a nested block.

    In C++, this is perfectly legal:

    int iVal = 8;
    //iVal == 8
    if (iVal == 8){
        int iVal = 5;
        //iVal == 5
    }
    //iVal == 8
    

    In C#, however you get a a compiler error:

    int iVal = 8;
    if(iVal == 8) {
        int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else
    }
    

    This makes sense if you look at generated MSIL - all the variables used by the function are defined at the start of the function. Take a look at this function:

    public static void Scope() {
        int iVal = 8;
        if(iVal == 8) {
            int iVal2 = 5;
        }
    }
    

    Below is the generated IL. Note that iVal2, which is defined inside the if block is actually defined at function level. Effectively this means that C# only has class and function level scope as far as variable lifetime is concerned.

    .method public hidebysig static void  Scope() cil managed
    {
      // Code size       19 (0x13)
      .maxstack  2
      .locals init ([0] int32 iVal,
               [1] int32 iVal2,
               [2] bool CS$4$0000)
    
    //Function IL - omitted
    } // end of method Test2::Scope
    

    C++ scope and object lifetime

    Whenever a C++ variable, allocated on the stack, goes out of scope it gets destructed. Remember that in C++ you can create objects on the stack or on the heap. When you create them on the stack, once execution leaves the scope, they get popped off the stack and gets destroyed.

    if (true) {
      MyClass stackObj; //created on the stack
      MyClass heapObj = new MyClass(); //created on the heap
      obj.doSomething();
    } //<-- stackObj is destroyed
    //heapObj still lives
    

    When C++ objects are created on the heap, they must be explicitly destroyed, otherwise it is a memory leak. No such problem with stack variables though.

    C# Object Lifetime

    In CLR, objects (i.e. reference types) are always created on the managed heap. This is further reinforced by object creation syntax. Consider this code snippet.

    MyClass stackObj;
    

    In C++ this would create an instance on MyClass on the stack and call its default constructor. In C# it would create a reference to class MyClass that doesn't point to anything. The only way to create an instance of a class is by using new operator:

    MyClass stackObj = new MyClass();
    

    In a way, C# objects are a lot like objects that are created using new syntax in C++ - they are created on the heap but unlike C++ objects, they are managed by the runtime, so you don't have to worry about destructing them.

    Since the objects are always on the heap the fact that object references (i.e. pointers) go out of scope becomes moot. There are more factors involved in determining if an object is to be collected than simply presence of references to the object.

    C# Object references

    Jon Skeet compared object references in Java to pieces of string that are attached to the balloon, which is the object. Same analogy applies to C# object references. They simply point to a location of the heap that contains the object. Thus, setting it to null has no immediate effect on the object lifetime, the balloon continues to exist, until the GC "pops" it.

    Continuing down the balloon analogy, it would seem logical that once the balloon has no strings attached to it, it can be destroyed. In fact this is exactly how reference counted objects work in non-managed languages. Except this approach doesn't work for circular references very well. Imagine two balloons that are attached together by a string but neither balloon has a string to anything else. Under simple ref counting rules, they both continue to exist, even though the whole balloon group is "orphaned".

    .NET objects are a lot like helium balloons under a roof. When the roof opens (GC runs) - the unused balloons float away, even though there might be groups of balloons that are tethered together.

    .NET GC uses a combination of generational GC and mark and sweep. Generational approach involves the runtime favouring to inspect objects that have been allocated most recently, as they are more likely to be unused and mark and sweep involves runtime going through the whole object graph and working out if there are object groups that are unused. This adequately deals with circular dependency problem.

    Also, .NET GC runs on another thread(so called finalizer thread) as it has quite a bit to do and doing that on the main thread would interrupt your program.

    0 讨论(0)
  • 2020-11-22 11:45

    I have to answer, too. The JIT generates tables together with the code from it's static analysis of variable usage. Those table entries are the "GC-Roots" in the current stack frame. As the instruction pointer advances, those table entries become invalid and so ready for garbage collection. Therefore: If it is a scoped variable, you don't need to set it to null - the GC will collect the object. If it is a member or a static variable, you have to set it to null

    0 讨论(0)
  • 2020-11-22 11:48

    As others have said you definitely want to call Dispose if the class implements IDisposable. I take a fairly rigid position on this. Some might claim that calling Dispose on DataSet, for example, is pointless because they disassembled it and saw that it did not do anything meaningful. But, I think there are fallacies abound in that argument.

    Read this for an interesting debate by respected individuals on the subject. Then read my reasoning here why I think Jeffery Richter is in the wrong camp.

    Now, on to whether or not you should set a reference to null. The answer is no. Let me illustrate my point with the following code.

    public static void Main()
    {
      Object a = new Object();
      Console.WriteLine("object created");
      DoSomething(a);
      Console.WriteLine("object used");
      a = null;
      Console.WriteLine("reference set to null");
    }
    

    So when do you think the object referenced by a is eligible for collection? If you said after the call to a = null then you are wrong. If you said after the Main method completes then you are also wrong. The correct answer is that it is eligible for collection sometime during the call to DoSomething. That is right. It is eligible before the reference is set to null and perhaps even before the call to DoSomething completes. That is because the JIT compiler can recognize when object references are no longer dereferenced even if they are still rooted.

    0 讨论(0)
  • 2020-11-22 11:48

    When an object implements IDisposable you should call Dispose (or Close, in some cases, that will call Dispose for you).

    You normally do not have to set objects to null, because the GC will know that an object will not be used anymore.

    There is one exception when I set objects to null. When I retrieve a lot of objects (from the database) that I need to work on, and store them in a collection (or array). When the "work" is done, I set the object to null, because the GC does not know I'm finished working with it.

    Example:

    using (var db = GetDatabase()) {
        // Retrieves array of keys
        var keys = db.GetRecords(mySelection); 
    
        for(int i = 0; i < keys.Length; i++) {
           var record = db.GetRecord(keys[i]);
           record.DoWork();
           keys[i] = null; // GC can dispose of key now
           // The record had gone out of scope automatically, 
           // and does not need any special treatment
        }
    } // end using => db.Dispose is called
    
    0 讨论(0)
提交回复
热议问题