2

I'm using the following method to search for a key in the registry and return it's value. I pass in a root key to search from and the relative path of the target as a string. As I parse the path, I use the previous key as a base to open the next level. I'ts strictly read-only so, is it necessary to close the base key after I am done with it? And if so, is this the way to do it?

public static string QueryRegistry (RegistryKey root, string path)
{
    return path.Split(Path.DirectorySeparatorChar)
    .Aggregate(root, (r, k) =>
    {
        var key = r?.OpenSubKey(k);
        r?.Close();
        return key;
    }).GetValue(null).ToString();
}
Dan Field
  • 20,885
  • 5
  • 55
  • 71
Cool Blue
  • 6,438
  • 6
  • 29
  • 68
  • `RegistryKey` is `IDisposable`: http://stackoverflow.com/a/9905046/1132334. [this](https://msdn.microsoft.com/en-us/library/microsoft.win32.registrykey(v=vs.90).aspx) suggests that is is sufficient to wrap the topmost key in `using` – Cee McSharpface Mar 02 '17 at 14:39
  • @dlatikay where it suggests that it is sufficient to only dispose root key? – Evk Mar 02 '17 at 14:41
  • Ok, yes, I knew it was `IDisposable` but I wasn't sure how to apply a using statement across function calls. Why only the top-most key? – Cool Blue Mar 02 '17 at 14:42
  • in their example, they're using `tempKey` from `openSubkey` without closing/explicit Dispose(). `RegistryKey` builds on [SafeHandle](https://referencesource.microsoft.com/#mscorlib/system/runtime/interopservices/safehandle.cs,ee2cc83fa843892c), and `OpenSubKey` gets another handle, and the implementation of `RegistryKey` does not show any mechanism to keep track of subsequent handles opened and disposing them along. So I *guess* (and therefore its a comment), that sub keys retrieved with `OpenSubKey` should also be properly disposed to not leak their handles. – Cee McSharpface Mar 02 '17 at 14:47
  • 1
    The only key named "tempKey" in their example is wrapped in using. So I would not assume any such behavior - better to dispose all keys. – Evk Mar 02 '17 at 14:49
  • you are right... how could I have not seen this. – Cee McSharpface Mar 02 '17 at 14:50
  • It _does_ imply that `Dispose` is sufficient though so that's good to know. – Cool Blue Mar 02 '17 at 14:59

1 Answers1

2

So your basic problem here is that you're creating an indeterminate number of IDisposable objects, and you're not sure when you'll be ready to close them. What I do in a situation like this is create a collection to track them, and then dispose everything in the collection when I'm done. There's some risk here that you won't properly dispose your objects though.

public static string QueryRegistry (RegistryKey root, string path)
{
    List<IDisposable> resourceTracker = new List<IDisposable>() { root };
    string ret = null;
    try 
    {
        ret = path.Split(Path.DirectorySeparatorChar)
        .Aggregate(root, (r, k) =>
        {
            var key = r?.OpenSubKey(k);
            if (key != null) 
            {
                resourceTracker.Add(key);
            }
            return key;
        }).GetValue(null).ToString();
    }
    finally
    {
        foreach (var res in resourceTracker)
        {
            res.Dispose();
        }
    }
    return ret;
}

You could try to do this in a CER, but I'm pretty sure opening the new keys count as allocations which mean the CLR wouldn't treat it as a CER anyway. But this should probably be safe enough.

This could be abstracted into a collection (somewhat like rory.ap suggested) like so:

public class DisposableCollection : IList<IDisposable>, IDisposable
{
    private List<IDisposable> disposables = new List<IDisposable>();

    #region IList<IDisposable> support

    public int Count
    {
        get
        {
            return ((IList<IDisposable>)disposables).Count;
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return ((IList<IDisposable>)disposables).IsReadOnly;
        }
    }

    public int IndexOf(IDisposable item)
    {
        return ((IList<IDisposable>)disposables).IndexOf(item);
    }

    public void Insert(int index, IDisposable item)
    {
        ((IList<IDisposable>)disposables).Insert(index, item);
    }

    public void RemoveAt(int index)
    {
        ((IList<IDisposable>)disposables).RemoveAt(index);
    }

    public void Add(IDisposable item)
    {
        ((IList<IDisposable>)disposables).Add(item);
    }

    public void Clear()
    {
        ((IList<IDisposable>)disposables).Clear();
    }

    public bool Contains(IDisposable item)
    {
        return ((IList<IDisposable>)disposables).Contains(item);
    }

    public void CopyTo(IDisposable[] array, int arrayIndex)
    {
        ((IList<IDisposable>)disposables).CopyTo(array, arrayIndex);
    }

    public bool Remove(IDisposable item)
    {
        return ((IList<IDisposable>)disposables).Remove(item);
    }

    public IEnumerator<IDisposable> GetEnumerator()
    {
        return ((IList<IDisposable>)disposables).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IList<IDisposable>)disposables).GetEnumerator();
    }

    public void AddRange(IEnumerable<IDisposable> range)
    {
        disposables.AddRange(range);
    }

    public IDisposable this[int index]
    {
        get
        {
            return ((IList<IDisposable>)disposables)[index];
        }

        set
        {
            ((IList<IDisposable>)disposables)[index] = value;
        }
    }
    #endregion

    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls


    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                foreach(var disposable in disposables)
                {
                    disposable.Dispose();
                }
            }

            // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
            // TODO: set large fields to null.

            disposedValue = true;
        }
    }

    // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
    // ~DisposableCollection() {
    //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
    //   Dispose(false);
    // }

    // This code added to correctly implement the disposable pattern.
    public void Dispose()
    {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(true);
        // TODO: uncomment the following line if the finalizer is overridden above.
        // GC.SuppressFinalize(this);
    }


    #endregion
}

Which could then be used like:

public static string QueryRegistry (RegistryKey root, string path)
{
    string ret = null;
    using (var resourceTracker = new DisposableCollection() { root })
    {
        ret = path.Split(Path.DirectorySeparatorChar)
        .Aggregate(root, (r, k) =>
        {
            var key = r?.OpenSubKey(k);
            if (key != null) 
            {
                resourceTracker.Add(key);
            }
            return key;
        }).GetValue(null).ToString();
    }
    return ret;
}

That class could be extended a bit as well to work in concurrent situations (say using a ConcurrentBag or ConcurrentQueue), and could be extended for other more specific needs (like a collection of RegistryKeys), and would be useful if you find yourself needing this logic in multiple places.

Dan Field
  • 20,885
  • 5
  • 55
  • 71
  • 1
    You could also make a disposable collection of the keys, and then just use a `using` block on the collection. The collection's `Dispose` method would loop through and dispose of each. Might be more robust, but yes a little more work. However, its a re-usable pattern so it might be worth it. – rory.ap Mar 02 '17 at 15:20
  • Good idea - I edited to give an example of at least abstracting out the collection more generically. I'm not sure how much value would get added by doing this specifically for `RegistryKey` rather than `IDisposable`, since the collection really only cares about calling `Dispose()`, but that depends on other use cases this might have in the app I suppose. – Dan Field Mar 02 '17 at 16:26
  • Hi @DanField, this is a brilliant solution for the case of undetermined lifecycle. In my case I know that the key is no longer required after I have used it's `.OpenSubKey` method to get the next level. That means I know when to close them. Given that, is there any problem with my method? Is there any advantage in wrapping in a `using` statement, or in bulk disposal, compared to individual disposal? – Cool Blue Mar 03 '17 at 00:45
  • The using statement encapsulates that try/finally logic to ensure dispose is called – Dan Field Mar 03 '17 at 01:37
  • Ok, so are you saying that I should really have a try/finally around the 'Close/Dispose` every iteration, to manage the risk of error on dispose? Is that the problem with my naive solution? Is that the problem? – Cool Blue Mar 03 '17 at 11:36
  • Yeah, if an exception were to occur you could miss actually closing/disposing and risk leaking an unmanaged resource (a handle to the registry) – Dan Field Mar 03 '17 at 13:00