c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud に公開中です。

考察は現在は主に Scrapbox で公表中です。

Programming は GitHub で開發中です。

C#で指定した型の定數を返すジェネリック函數を書くのに式木を使ふ

小ネタを出してゆく。

C#で數値の定數を得たい。指定した型の數値の定數を得たい。即ち次の樣なインターフェイスを想定する。

using System;

public class ConstNumber
{
  public static void Main()
  {
    int one = GetOne<int>();
    Console.WriteLine(one);
  }

  private static N GetOne<N>()
  {
    // Something
  }
}

実際は或る場所でのデフォルト値として定數が欲しく、且つ外から型を指定できる抽象的なメソッドが欲しく、且つ此のメソッドの外では型安全でありたかった。
意外と此れはできない。

N GetOne<N>() { return (int) 1; }
GetOne<int>();
// => error CS0029: Cannot implicitly convert type `int' to `N'
N GetOne<N>() { return (N) 1; }
GetOne<int>();
// => error CS0030: Cannot convert type `int' to `N'
N GetOne<N>() { return (N) ((object) 1); }
GetOne<int>();
// => OK.
GetOne<long>();
// => System.InvalidCastException: Specified cast is not valid.

ジェネリクスの引數として渡された型がどの型判定するコードは直ぐに書ける。typeof(N)を確かめればよい。
然し指定した型の定數を作るコードが書けない。何故か。其んな函數は用意されてゐないやうだ。
C++では此れは簡単にできる。

#include<iostream>

template <typename N>
N getOne() {
  return (N) 1;
}

int main() {
  std::cout << getOne<char>() << std::endl
            << getOne<double>() << std::endl
            << getOne<float>() << std::endl
            << getOne<int>() << std::endl
            << getOne<long>() << std::endl
            << getOne<short>() << std::endl
            << getOne<unsigned>() << std::endl
            << getOne<unsigned char>() << std::endl
            << getOne<unsigned long>() << std::endl
            << getOne<unsigned short>() << std::endl;
}

何故か。C++ではコンパイル時に其々の型の函數の實體が、型變數Nをchar, double, float, int, ...に置き換へた函數の實體が作られるから、Nへのキャスト(N) 1はint等へのキャスト(int) 1に書き換へられてコンパイルされる。此れは普通のコードだ。
C#ジェネリクスは此う云ふものではない。型變數Nの情報は実行時にも取得できる。C++の樣に普通のコードに変換されてコンパイルされるのではなく、ジェネリクスそのものとしてコンパイルされる。
よく思ひ出せばC#にもコードを生成する機能は在った。わたしはUnity3Dを使ってゐたので新しい機能は無いが、式木Expression Treeは在った。此れを使ふ。

using System;
using System.Linq.Expressions;

public class ConstNumber
{
  public static void Main()
  {
    Console.WriteLine(GetOne<int>());
  }

  private static N GetOne<N>()
  {
    return Expression.Lambda<Func<N>>(
      Expression.ConvertChecked(
        Expression.Constant(1, typeof(int)),
        typeof(N)
      )
    ).Compile()();
  }
}

whereガードでNに参照型が渡されるのを禁止できる。

using System;
using System.Linq.Expressions;

public class ConstNumber
{
  public static void Main()
  {
    Console.WriteLine(GetOne<int>());
  }

  private static N GetOne<N>() where N : struct
  {
    return Expression.Lambda<Func<N>>(
      Expression.ConvertChecked(
        Expression.Constant(1, typeof(int)),
        typeof(N)
      )
    ).Compile()();
  }
}

intの1を作り、N型にキャストする。bool等を指定してキャストに失敗したらSystem.InvalidOperationException実行時エラーを吐く。式木は実行時に展開される。コンパイラは、兔に角此のコードがN型を返す事はわかるので、通す。実行時に1をintやfloatにキャストする普通のコードに展開され、実行される。
一部の數値型に限定したい等有れば、型變數Nをチェックするコードを書ける。

using System;
using System.Linq;
using System.Linq.Expressions;

public class ConstNumber
{
  public static void Main()
  {
    Console.WriteLine(GetOne<byte>());
    Console.WriteLine(GetOne<char>());
    Console.WriteLine(GetOne<decimal>());
    Console.WriteLine(GetOne<double>());
    Console.WriteLine(GetOne<short>());
    Console.WriteLine(GetOne<int>());
    Console.WriteLine(GetOne<long>());
    Console.WriteLine(GetOne<ushort>());
    Console.WriteLine(GetOne<uint>());
    Console.WriteLine(GetOne<ulong>());
    Console.WriteLine(GetOne<sbyte>());
    Console.WriteLine(GetOne<float>());
  }

  private static N GetOne<N>() where N : struct
  {
    GuardWithNumericType(typeof(N));
    return Expression.Lambda<Func<N>>(
      Expression.ConvertChecked(
        Expression.Constant(1, typeof(int)),
        typeof(N)
      )
    ).Compile()();
  }

  private static void GuardWithNumericType(Type t)
  {
    var supportedTypes = new TypeCode[] {
      TypeCode.Byte,
      TypeCode.Char,
      TypeCode.Decimal,
      TypeCode.Double,
      TypeCode.Int16,
      TypeCode.Int32,
      TypeCode.Int64,
      TypeCode.UInt16,
      TypeCode.UInt32,
      TypeCode.UInt64,
      TypeCode.SByte,
      TypeCode.Single,
    };
    if (!supportedTypes.Contains(Type.GetTypeCode(t)))
    {
      throw new NotImplementedException();
    }
  }
}

後は何でも。