Search code examples
c#using-statement

Replace nested using-statement with one using statement


I find myself repeating myself with this code

using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)
{
  using (var aes = AesCryptoServiceProvider() { Key = ... }
  {

    // Read the IV at the beginning of the filestream

    using (var cryptoStream = new CryptoStream(fileStream, aes.CreateDecryptor(), CryptoStreamMode.Read)
    {

      // Actual code only using cryptoStream

    }
  }
}

and

using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write)
{
  using (var aes = AesCryptoServiceProvider() { Key = ... }
  {

    // Write the IV at the beginning of the filestream

    using (var cryptoStream = new CryptoStream(fileStream, aes.CreateDecryptor(), CryptoStreamMode.Write)
    {

      // Actual code only using cryptoStream

    }
  }
}

And I ask myself, if it is possible to replace this with something like this

using (var cryptoStream = new MyDecryptionStream(path))
{
  // Actual code
}

The actual code can be very different. It can be an image that has to be saved or an xml serialization.

I tried to implement my own Stream class, that casts all methods to a private property CryptoStream. But this didn't work out. It always broke at the counterpart, where I tried to read the IV at the beginning.


Solution

  • Here's an extremely rough example of what you're trying to do. There are a lot of places where improvement could be made, but it is a working sample that you can hopefully build on.

    First, we create a class that implements IDisposable. This allows us to use this class in using statements. This class will instantiate the three other objects we need, and handle disposing them, all by itself.

    class MyCryptoStream : IDisposable
    {
        private FileStream fileStream = null;
        private AesCryptoServiceProvider aes = null;
        public CryptoStream cryptoStream = null;
    
        public enum Mode
        {
            Write,
            Read
        }
    
        public MyCryptoStream(string filepath, Mode mode, byte[] key, byte[] iv = null)
        {
            if(mode == Mode.Write)
            {
                fileStream = new FileStream(filepath, FileMode.Open, FileAccess.Write);
                fileStream.Write(iv, 0, 16);
                aes = new AesCryptoServiceProvider() { Key = key, IV = iv };
    
                cryptoStream = new CryptoStream(fileStream, aes.CreateEncryptor(), CryptoStreamMode.Write);
            }
            else
            {
                iv = new byte[16];
                fileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read);
                fileStream.Read(iv, 0, 16);
                aes = new AesCryptoServiceProvider() { Key = key, IV = iv };
    
                cryptoStream = new CryptoStream(fileStream, aes.CreateDecryptor(), CryptoStreamMode.Read);
            }
        }
    
        #region IDisposable Support
        private bool disposedValue = false; // To detect redundant calls
    
        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    if (cryptoStream != null)
                    {
                        cryptoStream.Dispose();
                    }
                    if (aes != null)
                    {
                        aes.Dispose();
                    }
                    if (fileStream != null)
                    {
                        fileStream.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.
        // ~UsingReduction() {
        //   // 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
    
    }
    

    Now, we can use this class like so:

            string path = "..\\..\\test.txt";
            byte[] key = null;
            byte[] iv = null;
            using (AesCryptoServiceProvider myAes = new AesCryptoServiceProvider())
            {
                key = myAes.Key;
                iv = myAes.IV;
            }
            using (MyCryptoStream ur = new MyCryptoStream(path, MyCryptoStream.Mode.Write, key, iv))
            {
                using (StreamWriter sw = new StreamWriter(ur.cryptoStream))
                {
                    sw.Write("Test string");
                }
            }
            string text = string.Empty;
            using (MyCryptoStream ur = new MyCryptoStream(path, MyCryptoStream.Mode.Read, key))
            {
                using (StreamReader sr = new StreamReader(ur.cryptoStream))
                {
                    text = sr.ReadToEnd();
                }
            }
    

    If you run this example, you can see that it writes "Test string" to a file using the cryptostream, and then reads the same text back from that file. Checking the value of text, we can see that it is still "Test string", indicating that the procedure was successful.