⬆️ ⬇️

How to test the finalizer code (c #)

One of the obvious tasks is to test the code implemented in the finalizer of the dotnet class.

This note considers one of the ways to solve this problem.





For example, there is a class MyTemporaryFile (temporary file) that creates a unique temporary file in the constructor and should delete it in Dipose () or in the finalizer.



public class MyTemporaryFile : IDisposable { public string FileName { private set; get; } public MyTemporaryFile() { FileName = Path.GetTempFileName(); } public void Dispose() { Dispose(true); } ~MyTemporaryFile() { Dispose(false); } void Dispose(bool disposing) { if (disposing) { GC.SuppressFinalize(this); } DeleteFile(); } void DeleteFile() { if (FileName != null) { File.Delete(FileName); FileName = null; } } } 


')

The implementation of the Dispose pattern is pretty standard and was discussed at Habré . Surely there are some subtle points in this implementation, so keep this in mind in the “real” program.



But for some reason I did not find the discussion of the question, but how to test the code implemented in the finalizer.



It is clear that the “very naive” test implementation will not work.



  [Test] public void TestMyTemporaryFile_without_Dispose() { var temporaryFile = new MyTemporaryFile(); string createdTemporaryFileName = temporaryFile.FileName; Assert.IsTrue(File.Exists(createdTemporaryFileName)); temporaryFile = null; Assert.IsFalse(File.Exists(createdTemporaryFileName)); } 


The fact is that assigning null to the variable temporaryFile does not call the finalizer.



Met advice to call GC.WaitForPendingFinalizers (); , but for some reason this didn't help me in this test.



offtopic: Once upon a time, for some c # lectures, they talked about AppDomain . I didn’t really understand why I needed it. Well, you know how most lecturers say “ some common things ” for a “ certain average listener ”. I have never been able to understand the Dispose pattern from the words of a lecturer. The funny thing is that after I began to understand him a little bit, I hardly began to guess what the lecturer meant.



So, it turns out that using AppDomain you can easily prepare a test for the finalizer code:

  [Test] public void TestTemporaryFile_without_Dispose() { const string DOMAIN_NAME = "testDomain"; const string FILENAME_KEY = "fileName"; string testRoot = Directory.GetCurrentDirectory(); AppDomainSetup info = new AppDomainSetup { ApplicationBase = testRoot }; AppDomain testDomain = AppDomain.CreateDomain(DOMAIN_NAME, null, info); testDomain.DoCallBack(delegate { MyTemporaryFile temporaryFile = new MyTemporaryFile(); Assert.IsTrue(File.Exists(temporaryFile.FileName)); AppDomain.CurrentDomain.SetData(FILENAME_KEY, temporaryFile.FileName); }); string createdTemporaryFileName = (string)testDomain.GetData(FILENAME_KEY); Assert.IsTrue(File.Exists(createdTemporaryFileName)); AppDomain.Unload(testDomain); //       ( ),   Assert.IsFalse(File.Exists(createdTemporaryFileName)); } 




As you know, AppDomain.Unload (testDomain) ; unloads the code and clears the memory (including the finalizers).

This helps to “forcefully call” the finalizer and, accordingly, test its code.



Notes :

1. One of the lecturers advised in the finalizer to throw an exception (exception) with the words " call Dispose, idiot ". Somewhere it may be right, but if there is an unmanaged resource, it is necessary to provide for the finalizer too.

2. The implementation of the MyTemporaryFile class is very sketchy and not recommended for production use.

3. Most likely, the implementation of this test also has all sorts of subtle points, but many years of practice have never recorded the false triggering of this test.

4. I am pleased to read how to solve the problem of testing the finalizer in other ways or what are the disadvantages of this approach.



Thank,

Igor

Source: https://habr.com/ru/post/182702/



All Articles