Help us understand the problem. What is going on with this article?

C# 文字列を数値に変換する拡張メソッド

More than 5 years have passed since last update.

CSV,TSV を操作するC#クラスライブラリ

CST, TSV 等、文字区切りファイルをお手軽に読み書きできるライブラリを公開しました。
https://github.com/pierre3/HandyUtilities.Xsv

簡単な機能・使い方等はReadmeをご覧ください。
細部の作り込み(特に、読み込み後のデータ操作とか)はまだこれからですが、読み書きは普通にできます。

今回は、このライブラリでも使っているちょっと便利な拡張メソッドに関するネタです。

文字列を数値へ変換する拡張メソッド

C#で文字列から数値へ変換する処理といえば、以下のような記述をするのが一般的だと思います。

    //1. 変換できなかったら例外吐く場合
    var str = "123";
    var intValue = int.Parse(str);

    //2. 変換できなかったらなにか(規定値を設定とかを)したい場合
    int intValue2;
    if(!int.Parse(str,out intValue2))
    {
        intValue2 = -1;
    }

これでもいいのですが、あんまりスマートじゃない気がします。特に2.の方はものすごく面倒くさいです。
こういう時は、面倒な処理をまとめて拡張メソッドにしてしまいましょう!

デフォルト値の他にもnullを返すメソッドも欲しいですね。

public static int ToInt32(this string s)
{
    return int.Parse(s);
}

public static int? ToInt32OrNull(this string s)
{
    int result;
    if(int.TryParse(s,out result))
    {
        return result;
    }
    return null;
}

public static int ToInt32OrDefault(this string s, int defaultValue = default(int))
{
    int result;
    if(int.TryParse(s,out result))
    {
        return result;
    }
    return defaultValue;
}

Parse()メソッドのオーバーロードもちゃんと対応しましょう。
これで怖いものなしです。

//失敗したら例外投げる
static int ToInt32(this string s);
static int ToInt32(this string s, IFormatProvider formatProvider)
static int ToInt32(this string s, NumberStyles numberStyles)
static int ToInt32(this string s, NumberStyles numberStyles, IFormatProvider formatProvider)

//失敗したら規定値を返す。規定値は明示しなければdefault(T) になります。
static int ToInt32OrDefault(this string s, int defaultValue = default(int))
static int ToInt32OrDefault(this string s, NumberStyles numberStyles, int defaultValue = default(int))
static int ToInt32OrDefault(this string s, NumberStyles numberStyles, IFormatProvider formatProvider, int defaultValue = default(int))

//失敗したらNullを返す
static int? ToInt32OrNull(this string s)
static int? ToInt32OrNull(this string s, NumberStyles numberStyles)
static int? ToInt32OrNull(this string s, NumberStyles numberStyles, IFormatProvider formatProvider)

他の基本型についてもまとめて定義したい

のですが、すべての基本型について1つ1つ定義するのはいかにも大変そうです。
ジェネリックが使えれば良いのですが、Parse()、TryParse()メソッドはインターフェースでも基底クラスのメソッドでもないのでこれも無理です。

//こんなインターフェースが実装されていれば
public interface IParsable
{
    static IParsable Parse(string s);
    static bool TryParse(string s, out IParsable);
}

//こう書けるのだけれども
static T Parse<T> (this string s) where T: IParseable
{
    return T.Parse(s);
}

では、どうするか?
...
...
...
力技でゴリゴリ書きましょう! その結果がこちら になります。実に1000行以上に及ぶソースファイルになってしまいました。おかげで腱鞘炎に...

というのはうそです。こういう時は T4テンプレート の出番です。

T4テンプレートで生成する

以下のようなテンプレートを記述して、コードを生成します。

実際は 一部、Parse()のオーバーロードがなかったり、引数が違ったりするものもあってもう少し複雑になりますが、基本的には型名で回して、名前の部分を差し換えるだけでOKです。

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Globalization" #>
<#@ output extension=".cs" #>
<# 

var types = new Dictionary<string,string>()
{
        {"sbyte","SByte"},
        {"byte","Byte"},
        {"char","Char"},
        {"short","Int16"},
        {"ushort","UInt16"},
        {"int","Int32"},
        {"uint","UInt32"},
        {"long","Int64"},
        {"ulong","UInt64"},
        {"float","Float"},
        {"double","Double"},
        {"decimal","Decimal"},
        {"DateTime","DateTime"},
        {"bool","Boolean"}
};


#>
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Globalization;

namespace HandyUtil.Extensions.System
{
    public static partial class StringExt
    {
<# foreach(var typeName in types){ #>

        public static <#= typeName.Key #> To<#= typeName.Value #>(this string s)
        {
            return <#= typeName.Key #>.Parse(s);
        }

        public static <#= typeName.Key #>? To<#= typeName.Value #>OrNull(this string s)
        {
                <#= typeName.Key #> result;
                if(<#= typeName.Key #>.TryParse(s,out result))
                {
                        return result;
                }
                return null;
        }

        public static <#= typeName.Key #> To<#= typeName.Value #>OrDefault(this string s, 
            <#= typeName.Key #> defaultValue = default(<#= typeName.Key #>))
        {
                <#= typeName.Key #> result;
                if(<#= typeName.Key #>.TryParse(s,out result))
                {
                        return result;
                }
                return defaultValue;
        }
<# } #>
    }
}

テンプレートの完全版はこちらにあります。StringExt.t4.tt

おまけ

enum 版 もあります。こちらは手書きです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした