型変換 だいたい全部のやり方

基本用法

基本

C#言語の変数はコンパイル時に型が決まる静的型付け言語なので、異なる型の変数への代入はできない
int number = 10;
string name = "Ken";

// これは型が違うのでコンパイルが通らない
// name = number;  


// 暗黙的に型を持っている var も同じ
var number = 10;
var name = "Ken";

// これもコンパイルが通らない
// name = number;  

暗黙の型変換

変換後も情報が欠落しないような場合には、異なる型に代入できるものもある

・継承関係があり、派生クラスの型を継承元クラスまたはインタフェース型へ代入する場合
・数値型で狭範囲の型から広範囲の型へ代入する場合など

// 親クラス
class BaseClass
{
    public int ID { get; set; }
    public string Name { get; set; }
}
// 派生クラス
class DelivatedClass : BaseClass
{
    public int Telephone { get; set; }
}
 
class Program
{
    static void Main(string[] args)
    {
          // 32bitのintの値を64bitのlongに代入できる
          int a = 10;
          long b = a; 
 
          // 派生クラスは継承関係のある親クラスやインタフェースならOK
          DelivatedClass dc = new DelivatedClass();
          BaseClass bc = dc;
    } 
}

キャストとAS演算子

型変換する場合に問題が発生しうるが、コードを書く側がそれで問題ないと明示的に指定することにより変換できる場合もある

キャスト  変数の前に括弧で型を指定する。変換できない場合は実行時に例外がスローされる
AS演算子 参照型でのみ使用可能。変換できない場合はデフォルト値(null)が指定される
            // 64bitのlongを32bitのintに代入。
            long a = 10;
            //int b = a; これはできない
            int b = (int)a;  // 明示的にキャストすれば文法上はOK aの値が32bitを超えてると不正な値となる。

            // 変数は親クラス型だが、インスタンスは派生型なら情報の欠落はない。
            BaseClass bc1 = new DelivatedClass();
            DelivatedClass dc1 = (DelivatedClass)bc1;

            // インスタンス自体が親クラスだとコンパイルは通るが実行時エラーとなる
            BaseClass bc2 = new BaseClass();
            DelivatedClass dc2 = (DelivatedClass)bc2; //throws InvalidCastException

            // 参照型ではキャストと同じようにAS演算子も使える。
            // 変換できない場合は例外をスローするのではなくnullを返却する
            BaseClass bc3 = new BaseClass();
            DelivatedClass dc3 = bc3 as DelivatedClass;

ToStringメソッドとParseメソッド

全ての型はObjectクラスから派生しており、Object型はstring型を返却するToStringメソッドを持つ
よって、すべての型はToStringメソッドを持つが、返却される文字列が何を表すかは各クラスの定義次第

基本的なクラスや構造体の多くは文字列等から自身の型に変換するためのメソッドを持っている
// 値型でも参照型で文字列に変換可能
int number = 10;
string temp1 = number.ToString(); // "10"という文字列が返却される
 
DateTime dt = new DateTime(2020,10,31,23,48,32);
string temp2 = dt.ToString();     // "2020/10/31 23:48:32" という文字列が返却される(※カルチャ依存)
 

// 文字列からの変換メソッドを持つものが多い
int number2 = int.Parse("10");
DateTime dt2 = DateTime.Parse("2020/10/31 23:48:32");

Converter

標準ライブラリにある System.Convertクラスには型変換用のメソッドが多数定義されている
// こういうのがたくさんある
bool b1 = Convert.ToBoolean("true");
sbyte sb1 = Convert.ToSByte(-1);
Convert クラス (System)
基本データ型を別の基本データ型に変換します。

型変換演算子

継承関係などがなくても、変換演算子を実装すればキャストが可能となる

※型に変換演算子が実装されているかの確認手段がコード上にないのであまり使われてない
class AA
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int Telephone { get; set; }
 
    // 変換演算子の定義
    public static implicit operator BB(AA aa)
    {
        return new BB{ ID = aa.ID, Name = aa.Name };
    }
}
 
class BB 
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
}
 
class Program
{
    static void Main(string[] args)
    {
         AA aa = new AA();
         // 変換演算子があればOK
         BB bb = (BB)bb; 
}

独自コンバーター

型変換演算子は、コード上から実装有無が分からないし、継承関係の誤解となりやすく可読性が良くない。
System.Converterのようにコンバータークラスを別途用意する方が可読性はよい(かもしれない)

class AA : BaseClass
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int Telephone { get; set; }
 
    // 変換用のメソッドを追加定義
    public BB ConvertToBB()
    {
        return new BB{ ID = this.ID, Name = this.Name };
    }
}
// 又は、拡張メソッドとして追加
public static class AAEx
{
    public static BB ConvertToBB(this AA aa)
    {
        return new BB{ ID = aa.ID, Name = aa.Name };
    }
}

Object Mapping

例えばデータベースからリバース作成するエンティティクラスと、DTOクラス群との値の載せ替えなど、継承関係のないクラス間での変換が必要な場合は意外と多い
リフレクションを使うのでパフォーマンスにやや難はあるが、実行時のマップ(値の載せ替え)をする汎用の実装法もある。
例えば下記はpublic property間で型と名前が一致する場合に値をコピーするサンプルです。
using System.Reflection;
static T Map<T>(object from) where T : new()
{
    // 変換後の型のインスタンスを生成
    var ret = new T();
    // 変換後の型の全プロパティを対象に
    foreach (PropertyInfo pi in ret.GetType().GetProperties())
    {
        // Setterを取得 
        var setter = pi.GetSetMethod();
        // setterがpublicの場合
        if (setter != null)
        {
            // 変換元の同名同タイプでgetterがpublicなプロパティを取得
            var prop = from.GetType().GetProperties()
                        .Where(p => p.GetGetMethod() != null && p.Name == pi.Name && p.PropertyType == pi.PropertyType)
                        .SingleOrDefault();
 
             // あればその値を設定
            if(prop != null)
                pi.SetValue(ret,prop.GetValue(from,null),null);
        }
    }
    return ret;
}
こういう処理に特化したMapper用のライブライはいくつもあります
GitHub - AutoMapper/AutoMapper: A convention-based object-object mapper in .NET.
A convention-based object-object mapper in .NET. . Contribute to AutoMapper/AutoMapper development by creating an accoun...
higlabo/NetStandard/HigLabo.Mapper at master · higty/higlabo
HigLabo library provide 1.AI client library(OpenAI, Anthoripic, Groq) 2.Object Mapper(fastest in the world) 3.DbSharp(DA...