C# out of scope objects not getting collected

余生颓废 提交于 2021-01-28 11:45:47

问题


I'm trying to build my first unit tests and have a class that increments and decrements an instance counter in it's constructor and destructor respectively. I have a test to make sure this works but it fails, it seems that other instances of the class from my other tests aren't having their destructor called when they go out of scope.

public class Customer
{
    public Customer()
    {
        ++InstanceCount;
    }

    public Customer(int customerId)
    {
        CustomerId = customerId;
        ++InstanceCount;
    }

    ~Customer()
    {
        --InstanceCount;
    }

    public static int InstanceCount { get; private set; }
}
[TestClass]
public class CustomerTest
    {
    [TestMethod]
    public void FullNameLastNameEmpty()
    {
        //--Arrange
        Customer customer = new Customer {FirstName = "John"};
        const string expected = "John";

        //--Act
        string actual = customer.FullName;

        //--Assert
        Assert.AreEqual(expected, actual);
    }

    [TestMethod]
    public void StaticTest()
    {
        //--Arrange
        Customer[] customers =
        {
            new Customer {FirstName = "John"},
            new Customer {FirstName = "Jane"},
            new Customer {FirstName = "Jeff"} 
        };

        //--Act

        //--Assert
        Assert.AreEqual(3, Customer.InstanceCount);
    }
}

I'm not passing a reference anywhere so customer should be deallocated but it is not, I've even tried calling GC.Collect() at the start of StaticTest() to force calls to the destructor but still no luck.

Could someone explain why this is happening and how to fix it.


回答1:


C# is not C++. Destuctors, or rather finalizers, are a feature for very specific purposes, namely for handling the disposal of unmanaged resources held by your objects. Stuffing program logic into finalizers is one of the few very bad ideas you can have with C#. Why? Here's a quick rundown:

  • There is no guarantee on when an object will be collected when it goes out of scope.
  • Heck, there is no guarantee that a collection ever occurs if there's no memory pressure.
  • There's no guarantee on the ordering in which object finalizer's run.
  • There's no guarantee on when an object finalizer runs - could be right after it goes out of scope, could be a minute later.
  • Heck, there is no guarantee a finalizer ever runs.
  • There's no guarantee that, if it runs, it runs after your ctor completed. So in your case you could end up decrementing the instance count without incrementing it. Granted, this is unlikely to actually happen, but it just might.
  • Oh, it also negatively affects the performance of your entire app, but that's pretty minor compared to the evilness above.

Basically, when using finalizers, everything you know is wrong. Finalizers are so evil that there's a part two of that!

The guideline for using finalizers is - don't. Even if you are an experienced C# developer, the chances that you actually need a finalizer are very slim, while chances that you'll get something wrong while writing it are huge. If you're forced to work with unmanaged resources, follow the guidelines and use a SafeHandle.

If your requirement is to count instances, your best bet is using the IDisposable pattern. This is a cooperative way of doing that, meaning that the calling code will have to actually Dispose() of your object for the count to decrement. But it can't be any other way. The bottom line is that in C# there is no reliable way of doing something the moment the last reference to an object goes out of scope.

UPDATE:

I hope that I have discouraged you from ever writing finalizers, ever. Seriously, unless you're a pro and know for sure that you absolutely need the finalizer and can describe everything that happens on the CLR level with them, just don't. However. There is an ugly hack you can employ to get what you want. These two spells:

GC.Collect();
GC.WaitForPendingFinalizers();

should force a collection to happen, which will put your objects on the finalizer queue, and wait for all of them to finish. If you run that after every test case, it should give you the behaviour you expected. But please, for the sake of us all, don't use that in serious code. I've just shown you a terribly sharp, double-edged blade. Without any handle. That's on fire. That second method isn't even guaranteed to terminate.




回答2:


someone else already mentioned the difference between c++ destructor and c# finalizer so I am not going to repeat that.

In case you are wondering why your Test failed even after calling GC: Your variable never went out of scope. customers array holds references to all 3 Customer instances and the scope for customers array is StaticTest function. It will only be out of scope once StaticTest returns.

class Program
{
    static void Create()
    {
        Customer[] customers =
        {
            new Customer (),
            new Customer (),
            new Customer (),
        };
    }

    static void Main(string[] args)
    {
        Create();
        Console.WriteLine($"Before GC Count: {Customer.InstanceCount}");

        GC.Collect(0);
        GC.WaitForPendingFinalizers();

        Console.WriteLine($"After GC Count: {Customer.InstanceCount}");
    }
}

This will give you the expected result

Before GC Count: 3
After GC Count: 0




回答3:


adding to all these answers, below are my two cents.

If its c#,

  1. try add IDispoable to your class and create/override the dispose() method in your class. Then handle the decrement logic there.

  2. try to use the Test Teardown/finalize method and Test initialize methods to appropriately utilize the dispose of objects after every test case is completed.

In a real world scenario, once the object's scope is done, if we want to dispose them off, calling them explicitly by implementing IDispoable would make that object a candidate for Garbage collector. if it is what your intent is.



来源:https://stackoverflow.com/questions/59654198/c-sharp-out-of-scope-objects-not-getting-collected

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!