Search code examples
c#genericsfunctional-programmingnested-generics

Functional programming in c#: how do I nest using statements and allow code blocks to modify returned data?


I am wrapping my head around functional programming in c# so I can lower the amount of side effects in my code as well as make testing easier and generalizing my code so refactoring can be easier. However, I have problems figuring out how to nest using statements using a generalized using block. Consider the following:

public static class Disposable
{
    public static TResult Using<TDisposable, TResult>
    (
      Func<TDisposable> factory,
      Func<TDisposable, TResult> fn) where TDisposable : IDisposable
      {
            using (var disposable = factory())
            {
                return fn(disposable);
            }
      }
}

I invoke this code by using the following code sample:

Disposable.Using(
               StreamFactory.GetStream,
               stream => new byte[stream.Length].Tee(b => stream.Read(b, 0, (int)stream.Length)))

and I pass the output of this modified using statement into another method in the pipeline.

I did however come across a caveat, where I am stuck.

What if I want to use nested using statements, but make modifications to items returned that I want to pass along?

Consider the following by reusing the Disposable class I stubbed above:

        Disposable.Using(
            () => new PasswordDeriveBytes(PasswordConstants.CryptoKey, null),
            password => Disposable.Using(
                () => new RijndaelManaged(),
                symmetricKey =>
                    Disposable.Using(
                        () => symmetricKey.CreateEncryptor(password.GetBytes(PasswordConstants.KeySize/8), Encoding.ASCII.GetBytes(PasswordConstants.Cipher)),
                        encryptor => encryptor)
                ));

This code works... however, what if I wanted to change symmetricKey's encryption mode?

The following doesn't work:

        Disposable.Using(
                () => new PasswordDeriveBytes(PasswordConstants.CryptoKey, null),
                password => Disposable.Using(
                    () => new RijndaelManaged(),
                    symmetricKey =>
                        {
                            symmetricKey.Mode = CipherMode.CBC; // ← this causes an issue, and also the fact that I made a **code block** here
                            Disposable.Using(
                                () => symmetricKey.CreateEncryptor(password.GetBytes(PasswordConstants.KeySize/8), Encoding.ASCII.GetBytes(PasswordConstants.Cipher)),
                                encryptor => encryptor);
                        }
                    ));

What can I do to allow the modification of variables passed through the generalized Disposable.Using method I created above?


Solution

  • I think you're just missing a return. Try this.

    Disposable.Using(
        () => new PasswordDeriveBytes(PasswordConstants.CryptoKey, null),
        password => Disposable.Using(
            () => new RijndaelManaged(),
            symmetricKey =>
            {
                symmetricKey.Mode = CipherMode.CBC; 
                return Disposable.Using(
                    () => symmetricKey.CreateEncryptor(password.GetBytes(PasswordConstants.KeySize/8), Encoding.ASCII.GetBytes(PasswordConstants.Cipher)),
                    encryptor => encryptor);
            }));