2

I've implemented in C# counter using CRTP, but I don't know why it doesn't count down for some types:

using System;

using Type1 = C<char>;
using Type2 = C<int>;

class ExistingObjectCounter<CountedType>
{
    private static uint existingObjects = 0;

    protected ExistingObjectCounter()
    {
        ++existingObjects;
    }

    ~ExistingObjectCounter()
    {
        Console.WriteLine("Destruction of " + this + " number " + existingObjects);
        --existingObjects;
    }

    public static uint GetNumberOfLivingObjects
    {
        get
        {
            return existingObjects;
        }
    }
}

class C<T> : ExistingObjectCounter<C<T>>
{
    public C() 
    {}
    public C(C<T> m)
    {}
};

class ZliczaczObiektow
{
    public static void createManyCClassWithStringArgument()
    {
        for(int i=0; i<10;++i)
            new C<String>();
    }

    public static void Main()
    {
        Type1 c1 = new Type1(), c2 = new Type1(), c3 = new Type1(c2);
        var ws1  = new Type2();

        Console.WriteLine("existing objects of class " + c1.GetType() + ": "
                  + Type1.GetNumberOfLivingObjects);
        Console.WriteLine("existing objects of class " + ws1.GetType() + ": "
                  + Type2.GetNumberOfLivingObjects);

        createManyCClassWithStringArgument();

        {
            Type1 c4 = new Type1(), c5 = new Type1();
            var ws2  = new Type2();

            Console.WriteLine("existing objects of class " + c4.GetType() + ": "
                      + Type1.GetNumberOfLivingObjects);
            Console.WriteLine("existing objects of class " + ws2.GetType() + ": "
                      + Type2.GetNumberOfLivingObjects);

            createManyCClassWithStringArgument();
        }
        System.GC.Collect();

        Console.WriteLine("existing objects of class " + c1.GetType() + ": "
                  + Type1.GetNumberOfLivingObjects);
        Console.WriteLine("existing objects of class " + ws1.GetType() + ": "
                  + Type2.GetNumberOfLivingObjects);

        Console.WriteLine("existing objects of class C<String>:  "
          + C<String>.GetNumberOfLivingObjects);

        Console.ReadKey();
    }
}

the output is:

existing objects of class C`1[System.Char]:  3
existing objects of class C`1[System.Int32]: 1
existing objects of class C`1[System.Char]:  5
existing objects of class C`1[System.Int32]: 2
existing objects of class C`1[System.Char]:  5
existing objects of class C`1[System.Int32]: 2
existing objects of class C<String>:  20
Destruction of C`1[System.String] number 20
Destruction of C`1[System.String] number 19
Destruction of C`1[System.String] number 18
Destruction of C`1[System.String] number 17
Destruction of C`1[System.String] number 16
Destruction of C`1[System.String] number 15
Destruction of C`1[System.String] number 14
Destruction of C`1[System.String] number 13
Destruction of C`1[System.String] number 12
Destruction of C`1[System.String] number 11
Destruction of C`1[System.String] number 10
Destruction of C`1[System.String] number 9
Destruction of C`1[System.String] number 8
Destruction of C`1[System.String] number 7
Destruction of C`1[System.String] number 6
Destruction of C`1[System.String] number 5
Destruction of C`1[System.String] number 4
Destruction of C`1[System.String] number 3
Destruction of C`1[System.String] number 2
Destruction of C`1[System.String] number 1

EDIT: My question is why counters don't go down in some cases, but in another do? Why Type1 and Type2 don't have destructor being called in the end of the program, but on the other hand C parametrised with String has destructor being called?

baziorek
  • 2,502
  • 2
  • 29
  • 43
  • 1
    how do you invoke the garbage collector? (isnt in the code). I had a similar problem once and found out that even if invoked does not run necessarily...... http://stackoverflow.com/questions/4257372/how-to-force-garbage-collector-to-run the non accepted answer held the solution: GC.WaitForPendingFinalizers(); in addition to GC.Collect(); worked for me then. – Thomas Nov 10 '15 at 06:06
  • There are some [very good reasons](http://stackoverflow.com/questions/12265598/is-correct-to-use-gc-collect-gc-waitforpendingfinalizers) as to why this isn't a good idea. – Mark Feldman Nov 10 '15 at 06:08
  • 1
    @MarkFeldman From sad experience I know there are use cases where it is necessary (for example if you do sort of a windows explorer and load all possible application icons. It can overload what memory is admitted for the C# application if you don't start the garbage collection in between). Not a good idea but in some use cases sadly necessary. – Thomas Nov 10 '15 at 06:11
  • 1
    Sorry, I should have clarified...there are very good reasons why you should never *rely* on this behaviour. The example you describe is one where memory is indeed low and the GC will (hopefully) be more than happy to oblige. The specific example raised in the question though shouldn't cause that scenario, so it shouldn't be used to invoke dtor logic with the expectation that it will behave reliably (IMO). – Mark Feldman Nov 10 '15 at 06:21
  • 1
    @MarkFeldman No problem. I see it the same, that if there is no other way you can use the garbage collection but you should never rely on it if avoidable as it is just too unreliable. Maybe the OP can shed a bit of light if this is just for a bigger project where its unavoidable or "just" a test of some behaviour of the destructors,... ? – Thomas Nov 10 '15 at 06:32
  • You're misunderstanding, what GC root is. Look at accepted answer in the linked question, there is very clear explanation. – Dennis Nov 10 '15 at 06:44
  • @Dennis I'm not 100% I'm getting the point. GC root is clear. Although in this case there is no reference to each other. So the only rooted thing is the static counter but that one shouldn't root the class instances as the static counter exists "outside" the non static classes in that as far as I'm aware. So even though related, how does this explain the effect the OP is describing? – Thomas Nov 10 '15 at 06:50
  • @Thomas: my comment was addressed to OP, sorry. Local variables are GC roots. In debug build configuration, they will live until the method ends, and this is determined behavior. Nested scope (`{ }`) doesn't matter. For the sake of curiosity, run *release* build of this code. – Dennis Nov 10 '15 at 06:51
  • @dennis my question wasn't about your comment more about the duplicate as I'm not sure that it is a duplicate so I'm interested in how/if that oculd cause the observed behaviour in this case as from what I know about rooting it shouldn't be the case to cause this behaviour here. – Thomas Nov 10 '15 at 06:53
  • 1
    @grzgegor You had mentioned the garbage collector before. If it is about program end as the duplicate mark states you will have to do a new question there. If so then link to this question so that ti is not a duplicate but instead a different question and express what the difference is in your new question. – Thomas Nov 10 '15 at 07:13
  • @Thomas Thanks for the suggestion, I've asked two questions and unfortunately the question was marked as duplicates, so I edited and left only main question, but I will create new question – baziorek Nov 10 '15 at 07:18
  • @GrzegorzBazior make sure that you differentate from the current question and also maybe mention it that the current state of the "Edit" here is after this question was closed (to be on the safe side that no misunderstandings come up) – Thomas Nov 10 '15 at 07:19

2 Answers2

1

You cannot rely on ANY garbage collector behaviour. If you need destructor-type behaviour then make your classes inherit IDispose and call Dispose() manually.

Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
0

Mark Feldman hit the nail quite good with his answer and also his suggestion what you should normally do instead of relying on the garbage collector (as the GC is just too unreliable to start with even when called explicitely).

But to your question: The main problem you have boils down just to the garbage collector. Even when called there is no guarantee that it removes all no longer used (referenced) variables from memory (it is even worse with images which you need to handle completely differently than anything else). Thus what you are watching is the GC doing its job but not as you expect.

This is the sole reason for both of your questions: Why some object counts go down and others not (even though not referenced).

One thing that you can try though is, that after you call GC.Collect(); you also call GC.WaitForPendingFinalizers();. This CAN help but sadly as always with the garbage collector it is not 100% guaranteeable that it WILL help.

Thus if it is not absolutely necessary for you to use the garbage collector (absolutely is: You use so much memory if you don'T eliminate the unusude variables that your program crashes because of this) then you should avoid it with for example the method that Mark Feldman described in his answer.

Thomas
  • 2,886
  • 3
  • 34
  • 78
  • Your answer is wrong. GC isn't a thing, that produces some random behavior. You're misunderstanding GC root concept too, as like as OP does. – Dennis Nov 10 '15 at 06:50
  • I know the root concept in itself. But from experience I have had with GC behaviour even if I use GC.Collect() or wait for an hour sometimes variables that are NOT referenced in any way just didn't get collected and still used up memory in projects I had. In case of images the wait for pending finalizers helped and I could measure the effect. In some other cases I couldn't measure any effect it had. Thus even with GC.WaitForPendingFinalizers one shouldn't rely on all variables getting disposed when he calls the commands. So what is wrong there? – Thomas Nov 10 '15 at 06:54
  • @Denis you seem to be under the impression that a garbage collector is like a memory manager in unmanaged languages like C++. It isn't! There are all sorts of things going on under the hood in managed languages that you don't know about and from the application's perspective it can, and does, result in seemingly random behaviour at times. The GC, by explicit design, does not provide you with a reliable means to do what you're trying to do. Your best option is to work with it in the way in which it was designed to be used. – Mark Feldman Nov 10 '15 at 07:14