Architect's Log

I'm a Cloud Architect. I'm highly motivated to reduce toils with driving DevOps.

ファイル名の変更を拡張メソッドで実装する

ファイル名の変更が必要なケースは珍しくありませんが、System.IO.FileにもSystem.IO.FileInfoにも名前を変更するメソッドはありません。
そこで、ファイル名の変更を拡張メソッドでFileInfoに実装してみました。内部ではFileInfo.MoveToメソッドを使用しています。

IOExtensions.cs

using System;
using System.IO;

namespace Extensions {
    public static class IOExtensions {
        /// <summary>
        /// ファイルの名前を変更します。
        /// </summary>
        /// <param name="self">変更するファイルを示すFileInfo。</param>
        /// <param name="newName">変更後の名前。</param>
        public static void Rename(this FileInfo self, string newName) {
            if (Directory.Exists(self.FullName)) {
                throw new FileNotFoundException("指定されたパスはディレクトリです。path:" + self.FullName);
            }
            if (self.Exists == false) {
                throw new FileNotFoundException("指定されたファイルが見つかりません。path:" + self.FullName);
            }

            self.MoveTo(Path.Combine(self.DirectoryName, newName));
        }
    }
}

IOExtensionsTest.cs

using System;
using System.IO;
using Extensions;
using NUnit.Framework;

namespace ExtensionsTest {
    [TestFixture]    
    public class IOExtensionsTest {
        [Test]
        public void ファイルが存在しないときはエラー() {
            Exception ex = Assert.Throws<FileNotFoundException>(() => new FileInfo(@"c:\missing").Rename("new.txt"));
            StringAssert.StartsWith("指定されたファイルが見つかりません。", ex.Message);
        }

        [Test]
        public void ディレクトリの場合はエラー() {
            Exception ex = Assert.Throws<FileNotFoundException>(() => new FileInfo(@"C:\temporary").Rename("new.txt"));
            StringAssert.StartsWith("指定されたパスはディレクトリです。", ex.Message);
        }

        [Test]
        public void リネーム() {
            new FileInfo(@"C:\temporary\old.txt").Rename("new.txt");
            Assert.IsTrue(File.Exists(@"C:\temporary\new.txt"));
        }
    }
}

DataTableがNullまたは空かどうかを判定する拡張メソッドを定義する

DataTableがNullまたは空かどうかを判定する拡張メソッドを定義します。DataRowを保持していない場合に空と判断しています。
System.ComponentModel.IListSourceを実装したインスタンスであれば、DataTable以外でも使えます。

Extensions.cs

using System.ComponentModel;

namespace Extensions {
    public static class SystemComponentModelExtensions {
        /// <summary>
        /// 指定されたインスタンスがNullまたは要素数が0かどうかを示す値を取得します。
        /// </summary>
        /// <param name="self">IListSourceのインスタンス。</param>
        /// <returns>Nullまたは要素数が0の場合はtrue。それ以外の場合はfalse。</returns>
        public static bool IsNullOrEmpty(this IListSource self) {
            return (self == null) || (self.GetList().Count == 0);
        }
    }
}

Program.cs

using System;
using System.Data;
using Extensions;

namespace ConsoleApplication {
    class Program {
        static void Main(string[] args) {
            // Null
            DataTable dt = null;
            Console.WriteLine(dt.IsNullOrEmpty());

            // 行なし
            dt = new DataTable();
            dt.Columns.Add("col1", typeof(string));
            dt.Columns.Add("col2", typeof(string));
            dt.Columns.Add("col3", typeof(string));
            Console.WriteLine(dt.IsNullOrEmpty());

            // 行あり
            dt.Rows.Add("a", "b", "c");
            Console.WriteLine(dt.IsNullOrEmpty());

            Console.ReadLine();
        }
    }
}

実行結果

セッション状態値の存在チェックを拡張メソッドで実装する

セッション状態値の存在チェックを拡張メソッドで実装します。

ソースコード

using System;
using System.Web.SessionState;

namespace Extensions {
    public static class SystemWebSessionStateExtensions {
        /// <summary>
        /// 指定されたセッション状態値が存在するかどうかを示す値を取得します。
        /// </summary>
        /// <param name="self">HttpSessionStateのインスタンス。</param>
        /// <param name="name">セッション状態値のキー名。</param>
        /// <returns>セッション状態値が存在する場合はtrue。しない場合はfalse。</returns>
        public static bool Contains(this HttpSessionState self, string name) {
            return self[name] != null;
        }
    }
}

DataTableの内容をCSVとして出力する拡張メソッドを定義する

DataTableの内容をCSVとして出力する - プログラマーな日々
DataTableの内容をCSVとして出力するサンプルです。

先日のエントリーと等価な処理を拡張メソッドで定義します。

DataTableExtension.cs

using System.Data;
using System.Text;

namespace Extensions {
    public static class DataTableExtension {
        /// <summary>
        /// DataTableの内容をCSVの文字列として取得します。
        /// </summary>
        /// <param name="self">DataTableのインスタンス。</param>
        /// <param name="delimiter">区切り文字。</param>
        /// <param name="addHeader">DataColumn名を連結した文字列をヘッダとして付加する場合はtrue。付加しない場合はfalse。</param>
        /// <param name="excludeDataColumns">対象外のカラムを示すDataColumnの配列。</param>
        /// <returns></returns>
        public static string ToCsv(
            this DataTable self, 
            string delimiter = ",", 
            bool addHeader = true, 
            params DataColumn[] excludeDataColumns) {
            StringBuilder rowData = new StringBuilder();

            if (addHeader) {
                foreach (DataColumn column in self.Columns) {
                    if (DataTableExtension.IsExcludeColumn(column, excludeDataColumns))
                        continue;

                    rowData.Append(column).Append(delimiter);
                }
                rowData.Length = rowData.Length - delimiter.Length;
                rowData.AppendLine();
            }

            foreach (DataRow row in self.Rows) {
                foreach (DataColumn column in self.Columns) {
                    if (DataTableExtension.IsExcludeColumn(column, excludeDataColumns))
                        continue;

                    if (row.IsNull(column)) {
                        rowData.Append(string.Empty).Append(delimiter);
                    } else {
                        if (column.DataType == typeof(string)) {
                            string s = ((string) row[column]).Replace("\"", "\"\"");
                            s = DataTableExtension.GetEnclosedDataInQuotesIfContainsDelimiterOrNewLineOrQuotes(s, delimiter);
                            rowData.Append(s).Append(delimiter);
                        } else {
                            rowData.Append(row[column]).Append(delimiter);
                        }
                    }
                }
                rowData.Length = rowData.Length - delimiter.Length;
                rowData.AppendLine();
            }
            return rowData.ToString();

        }

        private static bool IsExcludeColumn(DataColumn column, DataColumn[] excludeDataColumns) {
            foreach (DataColumn excludeCol in excludeDataColumns) {
                if (excludeCol == column)
                    return true;
            }
            return false;
        }

        private static string GetEnclosedDataInQuotesIfContainsDelimiterOrNewLineOrQuotes(string data, string delimiter) {
            if ((data.Contains(delimiter)) || (data.Contains("\n")) || (data.Contains("\""))) {
                return string.Format("\"{0}\"", data);
            } else {
                return data;
            }
        }
    }
}

Program.cs

using System;
using System.Data;
using Extensions;

namespace ConsoleApplication {
    class Program {
        static void Main(string[] args) {
            DataTable dt = new DataTable();
            dt.Columns.Add("col1", typeof(string));
            dt.Columns.Add("col2", typeof(string));
            dt.Columns.Add("col3", typeof(string));
            dt.Rows.Add("a", "b\nb", "c");
            dt.Rows.Add("1", "2,2", "3");
            dt.Rows.Add("あ", "い\"い", "う");
            Console.WriteLine(dt.ToCsv(excludeDataColumns : dt.Columns[2]));
            Console.ReadLine();
        }
    }
}

実行結果

パラメータ情報を付加したCommandTextを取得する拡張メソッド

パラメータ情報を付加したCommandTextを取得する拡張メソッドを定義すると便利です。
SQLをログ出力するケースで使用します。(2012/02/11 読者さんからの指摘で追記)

using System;
using System.Data;
using System.Data.SqlClient;
using System.Text;

namespace DataAccess
{
    internal static class SqlCommandExtensions
    {
        /// <summary>
        /// パラメータ情報を付加したCommandTextを取得します。
        /// </summary>
        /// <param name="self">SqlCommandのインスタンス</param>
        /// <returns>パラメータ情報を付加したCommandText</returns>
        public static string CommandTextWithParameters(this SqlCommand self)
        {
            StringBuilder commandTextBuilder = new StringBuilder(self.CommandText);
            foreach(SqlParameter p in self.Parameters) {
                commandTextBuilder.AppendFormat(", {0} = {1}", p.ParameterName, p.Value);
            }
            return commandTextBuilder.ToString();
        }
    }
}

SqlCommandの戻り値パラメータ追加/値取得を拡張メソッドで実装する

SqlCommandの戻り値パラメータ追加/値取得を拡張メソッドで実装してみます。

using System;
using System.Data;
using System.Data.SqlClient;

namespace DataAccess {
    public static class SqlParameterCollectionExtensions {
        /// <summary>
        /// 戻り値のパラメータを追加します。
        /// </summary>
        /// <remarks>戻り値のパラメータの値は<see cref="ReturnValue"/>メソッドで取得します。</remarks>
        /// <param name="self">SqlParameterCollectionのインスタンス。</param>
        /// <returns>追加されたSqlParameterインスタンス。</returns>
        public static SqlParameter AddReturnParameter(this SqlParameterCollection self) {
            SqlParameter p = new SqlParameter();
            p.SqlDbType = SqlDbType.Int;
            p.Direction = ParameterDirection.ReturnValue;
            return self.Add(p);
        }

        /// <summary>
        /// 戻り値のパラメータの値を取得します。
        /// </summary>
        /// <param name="self">SqlParameterCollectionのインスタンス。</param>
        /// <returns>戻り値のパラメータの値。</returns>
        public static int ReturnValue(this SqlParameterCollection self) {
            foreach (SqlParameter p in self) {
                if (p.Direction != ParameterDirection.ReturnValue) continue;
                return (int)p.Value;
            }

            throw new InvalidOperationException("このSqlCommandには戻り値のパラメータがありません。");
        }
    }
}

配列の要素をカンマで連結した文字列を拡張メソッドで取得する

配列の要素をカンマで連結する拡張メソッドを定義すると、ログ出力時などに便利です。System.Arrayクラスを拡張すると全ての配列で使用することができます。

using System;

namespace Extensions {
    public static class SystemExtensions {
        /// <summary>
        /// 配列の文字列表現を取得します。
        /// </summary>
        /// <param name="self">配列のインスタンス。</param>
        /// <returns>取得した文字列。</returns>
        public static string ToText(this Array self) {
            StringBuilder b = new StringBuilder();
            foreach(object o in self) {
                b.AppendFormat("{0},", o);
            }
            b.Length--;
            return b.ToString();
        }
    }
}