0

I am coding something right now, and I am passing a string to the constructor. The manner in which the string is being generated is not changing in any way, but it (when I run the debug tools in Visual Studio Community) loses the value the first time, but shows a value most other times. Intermittently, the value is reporting that the string is null, or the value it should be.

Now, I really don't know how to document exactly what it is that I am doing, so here's the basics.

The first part is the definition of TempDir. I am using these temporary directories as testing directories that automatically kill themselves, and delete the contents, when the TempDir (and the test) go out of scope.

FINAL, WORKING, NO-LOST VALUE VERSION

public class TempDir : IDisposable
{
    private readonly string _path;
    public string ActiveDirectory => _path.Substring(_path.LastIndexOf('/') + 1, (_path.Length - _path.LastIndexOf('/') - 1));

    public string Path
    {
        get
        {
            return _path;
        }
    }
    public TempDir(string path) : this(path, false) { }
    public TempDir(string path, bool KillExisting)
    {
        _path = path;
        if(!KillExisting)
            return;
        if(Directory.Exists(_path))
            Directory.Delete(_path);
    }
    public void Dispose( )
    {
        if(System.IO.Directory.Exists(_path))
            Directory.Delete(_path, true);
    }

    public static implicit operator String(TempDir dir) => dir._path;
}

Now, this is the code that I am sending to a constructor. The TempDir's ActiveDirectory is being sent to a constructor, where NameOfThing should be the result of the first argument, and the second argument is also a string. The first one is intermittently working, the second always works.

TempDir dir = new TempDir(Environment.GetFolderPath(Environment.SpecialFolders.LocalApplicationData) + "/First/Second/NameOfThing")

This is the first run This is the run immediately after

I am seriously so lost on this, and from what I can tell, I think a thread may be changing something on me without my knowing

EDIT:

I can now "reliably" get it to pass every time, but I have to slowly walk through every single line of code. Running it normally without debugging through every line fails every time, but debugging slowly makes it pass every time.

Code constructing TempDir:

protected static string PackagesLocation = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "/Sloth/Notes/";
protected static TempDir TestPackLoc = new TempDir(PackagesLocation + "NPackageTests");
protected static NPackage TestPack = new NPackage(TestPackLoc.ActiveDirectory);

Test Method creating the page

[TestMethod]
public void GeneratesLayoutAndResourcesDirectory( )
{
    string key = "GeneratesLayoutAndResourcesDictionary";
    TestPack.CreatePage(key);
    if(!Directory.Exists(TestPackLoc + "/" + key + "/res") && !Directory.Exists(TestPackLoc + "/" + key + "/layout.xml"))
        Assert.Fail( );
}

Okay, so the behavior of the lost value was, I think, because C# was calling the garbage collector inappropriately. @mason mentioned that for the TempDir type, instead of implementing a destructor, I should implement the IDisposable. Now, it works reliably, and consistently. I have no clue why implementing the destructor did this, but swapping it out for IDisposable works just fine.

Credit for solution goes to @mason

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Rachael Dawn
  • 819
  • 6
  • 13
  • Where is NPage being instantiated? It's obviously passing in a null value – Joe Phillips Dec 22 '16 at 02:18
  • 1
    need more relevant code, screenshot are not enough. – Fredou Dec 22 '16 at 02:21
  • Need more *relevant code – Joe Phillips Dec 22 '16 at 02:22
  • Your TempDir is getting garbage collected. Check your how you instanciate NPage – R Quijano Dec 22 '16 at 02:28
  • It could be your implicit conversion operator "public static implicit operator String" sending empty path when you are passing TempDir as a string in constructor . Just a guess. – Hakunamatata Dec 22 '16 at 02:47
  • Why is `_path` `public` instead of `private`? – juharr Dec 22 '16 at 02:56
  • _path is public because I got mad and decided to debug it like that. – Rachael Dawn Dec 22 '16 at 05:42
  • First comment response: it only gives null sometimes though. ; Second comment: I don't know what else to post. What would help? ; Fourth comment: it might be. I may have to figure out why. ; Fifth comment: I will see if that's the issue. ; Sixth comment: I got super desperate to debug like that – Rachael Dawn Dec 22 '16 at 05:49
  • Updated comment for you. It's being used in a TestMethod because I learned that Visual Studio Community can run all code in tests without ever having to write code you just throw away. – Rachael Dawn Dec 22 '16 at 13:48
  • Well, the other comment thread has the solution. Just waiting for buddy to post it. The destructor of TempDir was wrecking the garbage collector, and sometimes providing null. I removed the destructor and implemented IDisposable, and magically, it started working every time again. – Rachael Dawn Dec 22 '16 at 15:08

2 Answers2

1

A destructor isn't really necessary here. The IDisposable pattern could be used instead. There's more to it than just implementing the interface, you also need to handle the object properly in any object that uses your IDisposable. You can implement a using statement.

using(var tempDir = new TempDir(arguments))
{
    //you can use tempDir inside here
} //tempDir's Dispose method is automatically called here
//since tempDir is out of scope here, the directory will have been deleted already

Anytime that an object implements IDisposable, you should wrap it in a using statement like above, or call its Dispose method in a finally block to make sure it gets removed properly. Here's what the try/catch/finally version would be like:

TempDir tempDir = null;

try
{
    tempDir = new TempDir(arguments);
    //now you can use tempDir here
}
catch(Exception ex)
{
    //log the exception. Optionally rethrow. Do not leave catch block empty
}
finally
{
    if(tempDir != null)
    {
        tempDir.Dispose();
    }
}

Most of the time I prefer the using block, because it makes the scope of the variable clearer.

You could also use a destructor to make sure that if someone forgets to call Dispose or wrap the object in a using block that the unmanaged resource (the directory) gets cleaned up properly.

public class TempDir : IDisposable
{
    private readonly string _path;

    public string ActiveDirectory => _path.Substring(_path.LastIndexOf('/') + 1, (_path.Length - _path.LastIndexOf('/') - 1));

    public string Path => _path;

    public TempDir(string path) : this(path, false) { }

    public TempDir(string path, bool KillExisting)
    {
        if(string.IsNullOrEmpty(path))
        {
            throw new ArgumentException($"{nameof(path)} cannot be null or empty.");
        }

        _path = path;

        if(KillExisting && Directory.Exists(_path))
        {
            Directory.Delete(_path);
        }

        //why not call Directory.CreateDirectory(_path) here?
    }

    public void Dispose( )
    {
        Cleanup();
    }

    ~TempDir()
    {
        Cleanup();
    }

    private void Cleanup()
    {
        if(Directory.Exists(_path))
        {
            Directory.Delete(_path, true);
        }
    }

    public static implicit operator String(TempDir dir) => dir._path;
}

There is no need to manually set objects to null as the garbage collector will handle de-allocating the memory for you.

mason
  • 31,774
  • 10
  • 77
  • 121
0

You are not encapsulating your private variables properly. It could be that you use the public _path variable outside of your class and inadvertently setting it to something else.

An improved version of your TempDir class might look like this:

public class TempDir
{
    private string _path;

    public TempDir(string path)
        : this(path, false) { }

    public TempDir(string path, bool killExisting)
    {
        _path = path;

        if (!killExisting) return;

        if (Directory.Exists(_path))
            Directory.Delete(_path);
    }

    public string Path => _path;

    public string ActiveDirectory
        => _path.Substring(_path.LastIndexOf('/') + 1, 
           _path.Length - _path.LastIndexOf('/') - 1);

    ~TempDir()
    {
        if (System.IO.Directory.Exists(_path))
            Directory.Delete(_path, true);
        _path = null;
    }

    public static implicit operator string(TempDir dir)
    {
        return dir._path;
    }
}
wj7ster
  • 9
  • 3
  • Hot damn that is some clean code. Can you elaborate on the => for the properties? I have only seen that used for delegates or LINQ (LINQ being the thing I want to avoid like the plague because I saw HUGE performance drops using LINQ) – Rachael Dawn Dec 22 '16 at 05:55
  • @JonathanSchmold It is C# 6.0 syntax for `public string Path { get { return _path; } }` – Maarten Dec 22 '16 at 13:49
  • @wj7ster Even better would be to make the `path` field `readonly`. – Maarten Dec 22 '16 at 13:50
  • Readonly prevents the destructor from nullifying the variable. Should just be cleaned up by the garbage collector I suppose? – Rachael Dawn Dec 22 '16 at 13:57
  • @JonathanSchmold No, that's not what [readonly](https://msdn.microsoft.com/en-us/library/acdd6hb7.aspx) means. Readonly means it can only be modified in the constructor. – mason Dec 22 '16 at 14:14
  • @mason That's really odd that they let you change it in the constructor, but not set it to null in the destructor. – Rachael Dawn Dec 22 '16 at 14:19
  • @JonathanSchmold Why would you need to set it to null in a destructor anyways? .NET is only going to call the destructor when the object is no longer in use, and therefore no code is going to access your `_path` variable anyways. – mason Dec 22 '16 at 14:22
  • Habit from when I was coding in C++. If you don't clean things up in C++, they sit there and take up more memory than necessary – Rachael Dawn Dec 22 '16 at 14:24
  • @JonathanSchmold C# is not C++. C# has a garbage collector. When your instance of `TempDir` is no longer in use it's eligible for garbage collection and the memory will be freed up. Manually setting a variable to null in a destructor is pointless because the destructor is only called when the object is garbage collected. Destructors should be used to clean up unmanaged resources (those that the garbage collector isn't aware of). Read more about [garbage collection](https://msdn.microsoft.com/en-us/library/0xy59wtx(v=vs.110).aspx). Not having to manually code cleanup makes the code simpler. – mason Dec 22 '16 at 14:33
  • I'm aware of how the garbage collector works. But, wouldn't overriding the destructor make the garbage collector work differently? Like, does it still clean up stored variables regardless of your destructor? – Rachael Dawn Dec 22 '16 at 14:42
  • @JonathanSchmold When an object is garbage collected, it's removed from memory. Any state of that object is also removed. That's why manually setting field/properties to `null` in a destructor is pointless. You're just doing what the garbage collector is about to do anyways. – mason Dec 22 '16 at 14:49
  • @JonathanSchmold See also [When should I create a destructor?](http://stackoverflow.com/questions/4898733) – mason Dec 22 '16 at 14:51
  • 1
    @JonathanSchmold Instead of a destructor, you should likely implement the [IDisposable](https://msdn.microsoft.com/en-us/library/system.idisposable(v=vs.110).aspx) pattern. This will ensure that your temporary directory is removed as soon as the object is out of scope, so long as you implement the pattern properly. – mason Dec 22 '16 at 14:54
  • HOW THE HELL! Dude, switching it to an IDisposable MAGICALLY made it work. Like, the issue I had no longer happens when I implemented IDisposable and removed the destructor. Can you post your comment as the answer so I can set it as the answer and you get the points? – Rachael Dawn Dec 22 '16 at 15:05