IronPython から .NET のコンストラクタを呼ぶ

例えば XNA の Graphics.Color 構造体は

名前説明
Color (Byte, Byte, Byte)Color の新しいインスタンスを初期化します。
Color (Byte, Byte, Byte, Byte)Color の新しいインスタンスを初期化します。
Color (Color, Byte)Color の新しいインスタンスを初期化します。
Color (Color, Single)Color の新しいインスタンスを初期化します。
Color (Single, Single, Single)Color の新しいインスタンスを初期化します。
Color (Single, Single, Single, Single)Color の新しいインスタンスを初期化します。
Color (Vector3)Color の新しいインスタンスを初期化します。
Color (Vector4)Color の新しいインスタンスを初期化します。
XNA Game Studio 4.0 Refresh | Microsoft Docs

といったコンストラクタを持っているわけですが。

IronPython から呼ぶ場合

Color(0, 100, 255)

と整数型を引数にして呼べば

public Color (
         byte r,
         byte g,
         byte b
)

パラメーター

r
カラーの赤成分 (0 〜 255)。
g
カラーの緑成分 (0 〜 255)。
b
カラーの青成分 (0 〜 255)。
XNA Game Studio 4.0 Refresh | Microsoft Docs

が呼ばれ、

Color(0.0, 0.4, 1.0)

浮動小数点型を引数にして呼べば

public Color (
         float r,
         float g,
         float b
)

パラメーター

r
カラーの赤成分 (0 〜 1.0)。
g
カラーの緑成分 (0 〜 1.0)。
b
カラーの青成分 (0 〜 1.0)。
XNA Game Studio 4.0 Refresh | Microsoft Docs

が呼ばれる、という動作をするのじゃないかな〜と直感的には思うんです。

……が。

IronPython 2.6.1 (2.6.10920.0) on .NET 2.0.50727.4927
Type "help", "copyright", "credits" or "license" for more information.
>>> import clr
>>> clr.AddReference('Microsoft.Xna.Framework')
>>> from Microsoft.Xna.Framework.Graphics import *
>>> Color(0, 100, 255)
<Microsoft.Xna.Framework.Graphics.Color object at 0x000000000000002B [{R:0 G:255 B:255 A:255}]>
>>> Color(0.0, 0.4, 1.0)
<Microsoft.Xna.Framework.Graphics.Color object at 0x0000000000000035 [{R:0 G:0 B:1 A:255}]>
>>> Color(0.0, 100.0, 255.0)
<Microsoft.Xna.Framework.Graphics.Color object at 0x000000000000002C [{R:0 G:100 B:255 A:255}]>

ちょ〜〜〜〜〜〜〜〜〜〜〜〜逆だし!

  • 整数型で呼び出すと 0 〜 1.0 の値な Single の引数を持つコンストラクタが呼び出されて(引数が上限の 1.0 まで切り詰められ) 1 以上の値は 1.0 → (内部値の) 255 へと。
  • 浮動小数点型で呼び出すと(小数点以下を切り捨て) 0 〜 255 の値な Byte の引数を持つコンストラクタが呼び出されてそのまま内部値へと。

こんな動作をしてしまっているのかな。

>>> from System import *
>>> Byte(0)
<System.Byte object at 0x000000000000002D [0]>
>>> Color(Byte(0), Byte(100), Byte(255))
<Microsoft.Xna.Framework.Graphics.Color object at 0x000000000000002E [{R:0 G:100
 B:255 A:255}]>
>>> Color(Single(0), Single(0.4), Single(1))
<Microsoft.Xna.Framework.Graphics.Color object at 0x0000000000000034 [{R:0 G:102
 B:255 A:255}]>

とあらかじめ(.NET の) System.Byte, System.Single 型を作って渡してやれば目的のコンストラクタが呼び出されるわけですけれども。

やっかいな仕様だなぁ。*1

追記

>>> Color(R=0, G=100, B=255)
<Microsoft.Xna.Framework.Graphics.Color object at 0x0000000000000031 [{R:0 G:100 B:255 A:0}]>
>>> Color(R=0.0, G=100.0, B=255.0)
<Microsoft.Xna.Framework.Graphics.Color object at 0x0000000000000032 [{R:0 G:100 B:255 A:0}]>

キーワード引数を使うと引数が整数型だろうが浮動小数点型だろうが内部値をそのまま指定できるみたい。コンストラクタがオーバーロードされているクラス/構造体を IronPython から呼び出す場合はこの内部値直接指定を使うのが正解かも。

ここらへんのコンストラクタ回りは Python と(.NET 標準言語の) C# で仕様が違う場所だから、いずれにしろ直感に頼るのは危険、かな。

*1:たぶん Byte と Single ではなく Int32 と Double だったなら素直にいくのでしょうが