ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

拡張メソッドで、あれ?って思ったこと

あれ?って思ったことシリーズ第二弾。

つい先日、Cで書かれた組み込み機器と通信するプログラムをC#で書きました。
で、テストを書くときに、@neueccさんのChaining Assertionを使いました。
これは、Assert書くのを気持ちよくしてくれるライブラリです。

こんな感じ。

int actual = 0;
            
//普通はこう書くけど
Assert.AreEqual(actual, 0);

//こんな感じで書ける
actual.Is(0);

int以外の場合にコンパイルエラー

冒頭でも書きましたが、その日の僕はCで書かれた組み込み機器と通信するプログラムを作っていました。受け渡しするデータはBYTE、WORD、DWORDなわけですよ。で、C#側でもbyte、ushort、uintって出てくるわけですね。で、それに対して次のようなコードを書くとですね・・・

byte actual = 0;
actual.Is(0);

f:id:ishikawa-tatsuya:20150516181208p:plain
えー、なんでやねん・・・ってことになります。

Chaining Assertionの定義はこんな感じです。

public static void Is<T>(this T actual, T expected, string message = "")

これって、第一引数のTが既にbyteだから第二引数の0に関してはbyte扱いしてくれても良さそうですよね。っていうか、これ実は拡張メソッドでなければコンパイル通るんです。

void Func<T>(T t1, T t2) { }

[TestMethod]
void Test()
{
    byte actual = 0;
    //これはOK。ほら、分かってるじゃん!
    Func(actual, 0);
}

でも、拡張にしたらコンパイルエラー

public static class Ex
{
    public static void Func<T>(this T t1, T t2) { }
}

[TestClass]
public class ExTest
{
    [TestMethod]
    void Test()
    {
        byte actual = 0;
        //コンパイルエラー
        //分かっておくれよ・・・
        actual.Func(0);
    }
}

しかも、これint以外は全滅かと思いきや、いけるのもあるw。なんでlongとかいけてるんだろ?
×がついているのはコンパイルエラーになります。

//×
byte by = 0;
by.Is(0);

//×
short s = 0;
s.Is(0);

//×
ushort us = 0;
us.Is(0);

int i = 0;
i.Is(0);

//×
uint ui = 0;
ui.Is(0);

long l = 0;
l.Is(0);

//×
ulong ul = 0;
ul.Is(0);

float f = 0;
f.Is(0);

double d = 0;
d.Is(0);

decimal dec = 0;
dec.Is(0);

まあ、数値を型にするの色々ルールあるし、なんかあるのかなー。できるだけintにするとかね。

で、新たな拡張作って、回避しました。

まあ、キャストするとかあるんですけど、折角Chaining Assertion使ってるのに、そんなの気持ちよくない。
今回は以下のような拡張メソッド作って回避しました。
拡張メソッドはより適合する方を選択してくれます。

public static class NumericAssertEx
{
    public static void Is(this byte actual, int expected, string message = "")
    {
        Assert.IsTrue(byte.MinValue <= expected && expected <= byte.MaxValue);
        Assert.AreEqual(actual, (byte)expected, message);
    }

    public static void Is(this short actual, int expected, string message = "")
    {
        Assert.IsTrue(short.MinValue <= expected && expected <= short.MaxValue);
        Assert.AreEqual(actual, (short)expected, message);
    }

    public static void Is(this ushort actual, int expected, string message = "")
    {
        Assert.IsTrue(ushort.MinValue <= expected && expected <= ushort.MaxValue);
        Assert.AreEqual(actual, (ushort)expected, message);
    }

    public static void Is(this uint actual, int expected, string message = "")
    {
        Assert.IsTrue(0 <= expected);
        Assert.AreEqual(actual, (uint)expected, message);
    }

    public static void Is(this ulong actual, int expected, string message = "")
    {
        Assert.IsTrue(0 <= expected);
        Assert.AreEqual(actual, (ulong)expected, message);
    }
}