環境変数の状態を保存する EnvironmentSaver

一時的に環境変数の値を変更する必要が出たのですが,変更した環境変数の値を元に戻す作業が煩わしくなってきました(変更するキーが増えたときに戻し忘れたりするなど・・・).そんな訳で,環境変数のある時点における状態に自動的に戻してくれる EnvironmentSaver を作成してみました.発想は C++ の boost::io_state_savers から.

上記の boost::io_state_savers のように C++ ではコンストラクタ/デストラクタを使う事が一般的ですが,C# では代わりに Dispose() メソッド(とユーザ側は using 構文)を使って行うようです.Dispose() で発生する可能性のある例外をどう扱うかなど気になる箇所もあるのですが,取りあえずと言うことで.

using Container = System.Collections.Generic;

namespace Clown {
    /* --------------------------------------------------------------------- */
    /// <summary>
    /// EnvironmentSaver
    /// 
    /// コンストラクタが呼ばれた時点の環境変数の状態を記憶し,Dispose()
    /// が呼ばれたときに環境変数の状態を戻すためのクラス.
    /// </summary>
    /* --------------------------------------------------------------------- */
    public class EnvironmentSaver : System.IDisposable {
        /* ----------------------------------------------------------------- */
        /// <summary>
        /// constructor
        /// </summary>
        /* ----------------------------------------------------------------- */
        public EnvironmentSaver() {
            this.target_ = System.EnvironmentVariableTarget.Process;
            this.env_ = GetVariables(this.target_);
        }

        /* ----------------------------------------------------------------- */
        /// <summary>
        /// constructor
        /// </summary>
        /* ----------------------------------------------------------------- */
        public EnvironmentSaver(System.EnvironmentVariableTarget target) {
            this.target_ = target;
            this.env_ = GetVariables(this.target_);
        }

        /* ----------------------------------------------------------------- */
        /// <summary>
        /// destructor
        /// </summary>
        /* ----------------------------------------------------------------- */
        ~EnvironmentSaver() {
            this.Dispose(false);
        }

        /* ----------------------------------------------------------------- */
        /// <summary>
        /// Dispose
        /// </summary>
        /* ----------------------------------------------------------------- */
        public void Dispose() {
            this.Dispose(true);
            System.GC.SuppressFinalize(this);
        }

        /* ----------------------------------------------------------------- */
        /// <summary>
        /// Dispose (protected)
        /// </summary>
        /* ----------------------------------------------------------------- */
        protected virtual void Dispose(bool disposing) {
            lock (this) {
                if (this.disposed_) return;
                this.disposed_ = true;
                Recover(this.env_, this.target_);
            }
        }

        /* ----------------------------------------------------------------- */
        /// <summary>
        /// GetVariables (private)
        /// </summary>
        /* ----------------------------------------------------------------- */
        private static Container.Dictionary<string, string> GetVariables(System.EnvironmentVariableTarget target) {
            var dest = new Container.Dictionary<string, string>();
            System.Collections.IDictionary v = System.Environment.GetEnvironmentVariables(target);
            foreach (System.Collections.DictionaryEntry elem in v) {
                dest.Add((string)elem.Key, (string)elem.Value);
            }
            return dest;
        }

        /* ----------------------------------------------------------------- */
        /// <summary>
        /// Recover (private)
        /// 
        /// 環境変数を引数 src に指定された状態に戻す.
        /// </summary>
        /* ----------------------------------------------------------------- */
        private static void Recover(Container.Dictionary<string, string> src, System.EnvironmentVariableTarget target) {
            if (src == null) return;

            try {
                System.Collections.IDictionary v = System.Environment.GetEnvironmentVariables(target);
                foreach (var elem in src) {
                    if (!v.Contains(elem.Key) || elem.Value != (string)v[elem.Key]) {
                        System.Environment.SetEnvironmentVariable(elem.Key, elem.Value, target);
                    }
                }

                if (v.Count > src.Count) {
                    // 新たに追加された環境変数を削除.
                    foreach (System.Collections.DictionaryEntry elem in v) {
                        string key = (string)elem.Key;
                        if (!src.ContainsKey(key)) System.Environment.SetEnvironmentVariable(key, "", target);
                    }
                }
            }
            catch (System.Exception /*e*/) {
                // TODO: Dispose から呼ばれるメソッドなので例外をどう扱うか.
                // 現状で把握してるものは SecurityException.
            }
            finally {
                src = null;
            }
        }

        private Container.Dictionary<string, string> env_;
        private System.EnvironmentVariableTarget target_;
        private bool disposed_ = false;
    }
}
サンプルコード

使い方は,以下のように using 構文と併用します.

using System;

namespace ExampleEnvironmentSaver {
    class Program {
        static void Main(string[] args) {
            PrintAllVariables();
            using (new Clown.EnvironmentSaver()) {
                // 一時的に環境変数の状態を変更する(値の変更,キーの追加,...).
                Environment.SetEnvironmentVariable("foo", "bar");
                Environment.SetEnvironmentVariable("TEMP", @"C:\Windows\Temp");
                PrintAllVariables();
            }
            PrintAllVariables();
        }

        static void PrintVariables(System.EnvironmentVariableTarget target) {
            foreach (System.Collections.DictionaryEntry elem in Environment.GetEnvironmentVariables(target)) {
                Console.WriteLine("\t{0}: {1}", (string)elem.Key, (string)elem.Value);
            }
        }

        static void PrintAllVariables() {
            Console.WriteLine("Process:");
            PrintVariables(EnvironmentVariableTarget.Process);
            Console.WriteLine();
        }
    }
}