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

はじめに

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

改訂履歴

  • 2026/04/18 : 初版公開。

本文

1. 環境

  • .NET 8.0
  • Visual Studio Community 2022

2. 実装

2-1. 型変換のヘルパークラス

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

メソッド名 内容
TryToString 文字列型に変換。
TryToType<T> ジェネリック型に変換。
TryToDateTime 日付型に変換。フォーマット指定できる。
TryToDateTimeOffset DateTimeOffset 型に変換。フォーマット指定できる。
※ソースは長くなるので、折り畳みます。
ConvertHelper.cs
using System.Globalization;

namespace MinimalApiExample.Utils;

/// <summary>
/// 型の変換に関連したメソッドを提供します。
/// </summary>
public static class ConvertHelper
{
    /// <summary>
    /// <see cref="ConvertHelper"/> における、既定の <see cref="DateTime"/> 値を取得します。
    /// </summary>
    /// <remarks><see cref="DateTime.MinValue"/> はタイムゾーンの変換やシリアライズ等で問題を起こし得るため、十分に余裕のある日時をアプリケーション側の最小値とします。</remarks>
    public static DateTime DefaultDateTime => new(2000, 1, 1);

    /// <summary>
    /// <see cref="ConvertHelper"/> における、既定の <see cref="DateTimeOffset"/> 値を取得します。
    /// </summary>
    /// <remarks><see cref="DateTimeOffset.MinValue"/> はタイムゾーンの変換やシリアライズ等で問題を起こし得るため、十分に余裕のある日時をアプリケーション側の最小値とします。</remarks>
    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>変換に成功した場合は <see langword="true"/>、失敗した場合は <see langword="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(
                null,
                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>変換に成功した場合は <see langword="true"/>、失敗した場合は <see langword="false"/>。</returns>
    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;
        }

        // 型の取得。
        Type type = typeof(T);

        // 型が DateTime の場合 TryToDateTime を試みる。
        if (type == typeof(DateTime))
        {
            bool isSuccess = TryToDateTime(
                value,
                out DateTime resultDateTime);

            result = (T)(object)resultDateTime;
            return isSuccess;
        }

        // 型が DateTimeOffset の場合 TryToDateTimeOffset を試みる。
        if (type == typeof(DateTimeOffset))
        {
            bool isSuccess = TryToDateTimeOffset(
                value,
                out DateTimeOffset 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 T 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">変換失敗した場合に返す既定値。<see langword="null"/> の場合は <see cref="DefaultDateTime"/> を使用します。</param>
    /// <param name="formats">使用する日時フォーマットの配列。</param>
    /// <returns>変換に成功した場合は <see langword="true"/>、失敗した場合は <see langword="false"/>。</returns>
    public static bool TryToDateTime(
        object? value,
        out DateTime result,
        DateTime? defaultValue = null,
        string[]? formats = null)
    {
        // 既定値の引数が null の場合は DefaultDateTime を使用する。
        DateTime 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 DateTime 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">変換失敗した場合に返す既定値。<see langword="null"/> の場合は <see cref="DefaultDateTimeOffset"/> を使用します。</param>
    /// <param name="formats">使用する日時フォーマットの配列。</param>
    /// <returns>変換に成功した場合は <see langword="true"/>、失敗した場合は <see langword="false"/>。</returns>
    public static bool TryToDateTimeOffset(
        object? value,
        out DateTimeOffset result,
        DateTimeOffset? defaultValue = null,
        string[]? formats = null)
    {
        // 既定値の引数が null の場合は DefaultDateTimeOffset を使用する。
        DateTimeOffset 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 DateTimeOffset parsedValue))
        {
            result = parsedValue;
            return true;
        }

        result = returnDefaultValue;
        return false;
    }
}

2-2. 拡張メソッド

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

メソッド名 内容
ToStringOrDefault 文字列型に変換。失敗時は既定値。
ToTypeOrDefault<T> ジェネリック型に変換。失敗時は既定値。
ToDateTimeOrDefault 日付型に変換。失敗時は既定値。
ToDateTimeOffsetOrDefault DateTimeOffset 型に変換。失敗時は既定値。
※ソースは長くなるので、折り畳みます。
ObjectExtensions.cs
namespace MinimalApiExample.Utils;

/// <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 T result, defaultValue);
        return result;
    }

    /// <summary>
    /// 指定したオブジェクトを <see cref="DateTime"/> 型に変換します。変換に失敗した場合は、指定した既定値を返します。
    /// </summary>
    /// <param name="value">変換対象のオブジェクト。</param>
    /// <param name="defaultValue">変換失敗した場合に返す既定値。<see langword="null"/> の場合は <see cref="ConvertHelper.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 DateTime result, defaultValue, formats);
        return result;
    }

    /// <summary>
    /// 指定したオブジェクトを <see cref="DateTimeOffset"/> 型に変換します。変換に失敗した場合は、指定した既定値を返します。
    /// </summary>
    /// <param name="value">変換対象のオブジェクト。</param>
    /// <param name="defaultValue">変換失敗した場合に返す既定値。<see langword="null"/> の場合は <see cref="ConvertHelper.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 DateTimeOffset result, defaultValue, formats);
        return result;
    }
}

3. 使用例

使用例を列挙します。

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))
{
    // 成功
}

4. 参考

おわりに

DateTime や DateTimeOffset の既定値は null でも良いと思います。

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?