Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

C# 型変換のヘルパークラスを作ってみた

0
Posted at

はじめに

備忘録です。型変換のヘルパークラスを作ってみました。

型変換のヘルパークラス

型変換関数を提供する ConvertHelper では、以下のようなメソッドを提供しています。DefaultDateTime や s_dateTimeFormats は必要に応じて変更してください。

メソッド名 内容
TryToString 文字列型に変換。
TryToType<T> T 型に変換。
TryToDateTime 日付型に変換。フォーマット指定できる。
TryToDateTimeOffset DateTimeOffset 型に変換。フォーマット指定できる。
ソースは長くなるので、折り畳みます。
ConvertHelper.cs
public static class ConvertHelper
{
    /// <summary>
    /// <see cref="ConvertHelper"/> における、既定の <see cref="DateTime"/> 値を取得します。
    /// </summary>
    public static DateTime DefaultDateTime => new(2000, 1, 1);

    /// <summary>
    /// <see cref="ConvertHelper"/> における、既定の <see cref="DateTimeOffset"/> 値を取得します。
    /// </summary>
    public static DateTimeOffset DefaultDateTimeOffset => new(DefaultDateTime, TimeSpan.Zero);

    /// <summary>
    /// 変換処理で使用される <see cref="DateTime"/> の既定フォーマット一覧です。
    /// </summary>
    private static readonly string[] s_dateTimeFormats =
    [
        // 連続
        "yyyyMMdd",
        "yyyyMMddHHmmss",
        "yyyyMMddHHmmssfff",
        "yyyyMMddHHmmssfffffff",

        // スラッシュ
        "yyyy/MM/dd",
        "yyyy/MM/dd HH:mm:ss",
        "yyyy/MM/dd HH:mm:ss.fff",
        "yyyy/MM/dd HH:mm:ss.fffffff",

        // ハイフン
        "yyyy-MM-dd",
        "yyyy-MM-dd HH:mm:ss",
        "yyyy-MM-dd HH:mm:ss.fff",
        "yyyy-MM-dd HH:mm:ss.fffffff",
    ];

    /// <summary>
    /// 変換処理で使用される <see cref="DateTimeOffset"/> の既定フォーマット一覧です。
    /// </summary>
    private static readonly string[] s_dateTimeOffsetFormats =
    [
        // ISO 8601 / RFC 3339 形式
        "yyyy-MM-ddTHH:mm:sszzz",
        "yyyy-MM-ddTHH:mm:ss.fffzzz",
        "yyyy-MM-ddTHH:mm:ss.fffffffzzz",
        "yyyy-MM-ddTHH:mm:ssZ",
        "yyyy-MM-ddTHH:mm:ss.fffZ",
        "yyyy-MM-ddTHH:mm:ss.fffffffZ"
    ];

    /// <summary>
    /// 指定したオブジェクトを文字列形式に変換できるかどうかを試行し、成功したかどうかを示す値を返します。
    /// </summary>
    /// <param name="value">変換対象のオブジェクト。</param>
    /// <param name="result">変換結果を返す出力パラメータ。</param>
    /// <param name="defaultValue">変換失敗した場合に返す既定値。</param>
    /// <returns>変換に成功した場合は true、失敗した場合は false。</returns>
    public static bool TryToString(
        object? value,
        out string result,
        string defaultValue = "")
    {
        if (value is null || value == DBNull.Value)
        {
            result = defaultValue;
            return false;
        }

        if (value is IFormattable formattableValue)
        {
            // Culture 依存を回避して、文字列形式に変換する
            result = formattableValue.ToString(
                format: null,
                formatProvider: CultureInfo.InvariantCulture);

            return true;
        }

        result = value.ToString() ?? defaultValue;
        return true;
    }

    /// <summary>
    /// 指定したオブジェクトを型 <typeparamref name="T"/> に変換できるかどうかを試行し、成功したかどうかを示す値を返します。
    /// </summary>
    /// <typeparam name="T">変換先の値型で <see cref="IParsable{T}"/> を実装している型。</typeparam>
    /// <param name="value">変換対象のオブジェクト。</param>
    /// <param name="result">変換結果を返す出力パラメータ。</param>
    /// <param name="defaultValue">変換失敗した場合に返す既定値。</param>
    /// <returns>変換に成功した場合は true、失敗した場合は false。</returns>
    /// <exception cref="NotSupportedException"></exception>
    public static bool TryToType<T>(
        object? value,
        out T result,
        T defaultValue = default)
        where T : struct, IParsable<T>
    {
        // null チェック
        if (value is null || value == DBNull.Value)
        {
            result = defaultValue;
            return false;
        }

        // 型が既に T の場合は、そのまま返す
        if (value is T t)
        {
            result = t;
            return true;
        }

        // 型取得
        var type = typeof(T);

        // 型が DateTime の場合 TryToDateTime を試みる
        if (type == typeof(DateTime))
        {
            bool isSuccess = TryToDateTime(value, out var resultDateTime);
            result = (T)(object)resultDateTime;
            return isSuccess;
        }

        // 型が DateTimeOffset の場合 TryToDateTimeOffset を試みる
        if (type == typeof(DateTimeOffset))
        {
            bool isSuccess = TryToDateTimeOffset(value, out var resultDateTime);
            result = (T)(object)resultDateTime;
            return isSuccess;
        }

        // 文字列型に変換する
        if (!TryToString(value, out string stringValue)
            || string.IsNullOrWhiteSpace(stringValue))
        {
            result = defaultValue;
            return false;
        }
        else
        {
            stringValue = stringValue.Trim();
        }

        // Culture 依存を回避して、指定の型に Parse する
        if (T.TryParse(
            stringValue,
            CultureInfo.InvariantCulture,
            out var parsedValue))
        {
            result = parsedValue;
            return true;
        }

        // Parse に失敗した場合は Convert.ChangeType を試みる
        // Parse と Convert で対応している型変換の条件が異なるため
        // 二重で変換処理を行っている
        if (value is IConvertible)
        {
            try
            {
                result = (T)Convert.ChangeType(
                    value!,
                    Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T),
                    CultureInfo.InvariantCulture);

                return true;
            }
            catch
            {
                result = defaultValue;
                return false;
            }
        }

        result = defaultValue;
        return false;
    }

    /// <summary>
    /// 指定したオブジェクトを <see cref="DateTime"/> 型に変換できるかどうかを試行し、成功したかどうかを示す値を返します。
    /// </summary>
    /// <param name="value">変換対象のオブジェクト。</param>
    /// <param name="result">変換結果を返す出力パラメータ。</param>
    /// <param name="defaultValue">変換失敗した場合に返す既定値。null の場合は <see cref="DefaultDateTime"/> を使用します。</param>
    /// <param name="formats">使用する日時フォーマットの配列。</param>
    /// <returns>変換に成功した場合は true、失敗した場合は false。</returns>
    public static bool TryToDateTime(
        object? value,
        out DateTime result,
        DateTime? defaultValue = null,
        string[]? formats = null)
    {
        // 既定値の引数が null の場合は DefaultDateTime を使用する
        var returnDefaultValue = defaultValue ?? DefaultDateTime;

        // null チェック
        if (value is null || value == DBNull.Value)
        {
            result = returnDefaultValue;
            return false;
        }

        // 型が既に DateTime の場合は、そのまま返す
        if (value is DateTime t)
        {
            result = t;
            return true;
        }

        // 文字列型に変換する
        if (!TryToString(value, out string stringValue)
            || string.IsNullOrWhiteSpace(stringValue))
        {
            result = returnDefaultValue;
            return false;
        }
        else
        {
            stringValue = stringValue.Trim();
        }

        // フォーマットが指定されていない場合は、既定のフォーマットを用意する
        if (formats is null || formats.Length == 0)
        {
            formats = s_dateTimeFormats;
        }

        // Culture 依存を回避して DateTime 型に変換する
        if (DateTime.TryParseExact(
            stringValue,
            formats,
            CultureInfo.InvariantCulture,
            DateTimeStyles.None,
            out var parsedValue))
        {
            result = parsedValue;
            return true;
        }

        result = returnDefaultValue;
        return false;
    }

    /// <summary>
    /// 指定したオブジェクトを <see cref="DateTimeOffset"/> 型に変換できるかどうかを試行し、成功したかどうかを示す値を返します。
    /// </summary>
    /// <param name="value">変換対象のオブジェクト。</param>
    /// <param name="result">変換結果を返す出力パラメータ。</param>
    /// <param name="defaultValue">変換失敗した場合に返す既定値。null の場合は <see cref="DefaultDateTimeOffset"/> を使用します。</param>
    /// <param name="formats">使用する日時フォーマットの配列。</param>
    /// <returns>変換に成功した場合は true、失敗した場合は false。</returns>
    public static bool TryToDateTimeOffset(
        object? value,
        out DateTimeOffset result,
        DateTimeOffset? defaultValue = null,
        string[]? formats = null)
    {
        // 既定値の引数が null の場合は DefaultDateTimeOffset を使用する
        var returnDefaultValue = defaultValue ?? DefaultDateTimeOffset;

        // null チェック
        if (value is null || value == DBNull.Value)
        {
            result = returnDefaultValue;
            return false;
        }

        // 型が既に DateTimeOffset の場合は、そのまま返す
        if (value is DateTimeOffset t)
        {
            result = t;
            return true;
        }

        // 文字列型に変換する
        if (!TryToString(value, out string stringValue)
            || string.IsNullOrWhiteSpace(stringValue))
        {
            result = returnDefaultValue;
            return false;
        }
        else
        {
            stringValue = stringValue.Trim();
        }

        // フォーマットが指定されていない場合は、既定のフォーマットを用意する
        if (formats is null || formats.Length == 0)
        {
            formats = s_dateTimeOffsetFormats;
        }

        // Culture 依存を回避して DateTimeOffset 型に変換する。
        if (DateTimeOffset.TryParseExact(
            stringValue,
            formats,
            CultureInfo.InvariantCulture,
            DateTimeStyles.None,
            out var parsedValue))
        {
            result = parsedValue;
            return true;
        }

        result = returnDefaultValue;
        return false;
    }
}

拡張メソッド

ConvertHelper に対して、以下のような拡張メソッドを用意すると、より便利です。

メソッド名 内容
ToStringOrDefault 文字列型に変換。失敗時は既定値。
ToTypeOrDefault<T> T 型に変換。失敗時は既定値。
ToDateTimeOrDefault 日付型に変換。失敗時は既定値。
ToDateTimeOffsetOrDefault DateTimeOffset 型に変換。失敗時は既定値。
ソースは長くなるので、折り畳みます。
ObjectExtensions.cs
/// <summary>
/// <see cref="object"/> 型の拡張メソッドを提供する静的クラスです。
/// </summary>
public static class ObjectExtensions
{
    /// <summary>
    /// 指定したオブジェクトを文字列形式に変換します。変換に失敗した場合は、指定した既定値を返します。
    /// </summary>
    /// <param name="value">変換対象のオブジェクト。</param>
    /// <param name="defaultValue">変換失敗した場合に返す既定値。</param>
    /// <returns><paramref name="value"/> の文字列形式、または既定値を返します。</returns>
    public static string ToStringOrDefault(
        this object? value,
        string defaultValue = "")
    {
        ConvertHelper.TryToString(value, out string result, defaultValue);
        return result;
    }

    /// <summary>
    /// 指定したオブジェクトを型 <typeparamref name="T"/> に変換します。変換に失敗した場合は、指定した既定値を返します。
    /// </summary>
    /// <typeparam name="T">変換先の値型で <see cref="IParsable{T}"/> を実装している型。</typeparam>
    /// <param name="value">変換対象のオブジェクト。</param>
    /// <param name="defaultValue">変換失敗した場合に返す既定値。</param>
    /// <returns>変換に成功した場合は型 <typeparamref name="T"/> の値、失敗した場合は規定値を返します。</returns>
    public static T ToTypeOrDefault<T>(
        this object? value,
        T defaultValue = default)
        where T : struct, IParsable<T>
    {
        ConvertHelper.TryToType(value, out var result, defaultValue);
        return result;
    }

    /// <summary>
    /// 指定したオブジェクトを <see cref="DateTime"/> 型に変換します。変換に失敗した場合は、指定した既定値を返します。
    /// </summary>
    /// <param name="value">変換対象のオブジェクト。</param>
    /// <param name="defaultValue">変換失敗した場合に返す既定値。null の場合は <see cref="DefaultDateTime"/> を使用します。</param>
    /// <param name="formats">使用する日時フォーマットの配列。</param>
    /// <returns>変換に成功した場合は <see cref="DateTime"/>、失敗した場合は規定値を返します。</returns>
    public static DateTime ToDateTimeOrDefault(
        this object? value,
        DateTime? defaultValue = null,
        string[]? formats = null)
    {
        ConvertHelper.TryToDateTime(value, out var result, defaultValue, formats);
        return result;
    }

    /// <summary>
    /// 指定したオブジェクトを <see cref="DateTimeOffset"/> 型に変換します。変換に失敗した場合は、指定した既定値を返します。
    /// </summary>
    /// <param name="value">変換対象のオブジェクト。</param>
    /// <param name="defaultValue">変換失敗した場合に返す既定値。null の場合は <see cref="DefaultDateTimeOffset"/> を使用します。</param>
    /// <param name="formats">使用する日時フォーマットの配列。</param>
    /// <returns>変換に成功した場合は <see cref="DateTimeOffset"/>、失敗した場合は規定値を返します。</returns>
    public static DateTimeOffset ToDateTimeOffsetOrDefault(
        this object? value,
        DateTimeOffset? defaultValue = null,
        string[]? formats = null)
    {
        ConvertHelper.TryToDateTimeOffset(value, out var result, defaultValue, formats);
        return result;
    }
}

使用例

使用例を列挙します。

Sample.cs
// string 型に変換する
var stringValue = value.ToStringOrDefault();

// int 型に変換する (失敗時は -1 が返る)
var intValue = value.ToTypeOrDefault<int>(-1);

// フォーマットを指定して DateTime 型に変換する
var dateTimeValue = value.ToDateTimeOrDefault(formats: formats);

// DateTimeOffset 型に変換できるか検証する
if (ConvertHelper.TryToDateTimeOffset(value, out var parsed))
{
    // 成功
}

おわりに

DateTime や DateTimeOffset の既定値は null でも良いと思います。MinValue はやめておきましょう。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?