SelectManyでreturn nullはご法度 #linq #csharp 訂正あり

久しぶりのブログということで、細かいことを少しだけ。

編集:指摘に合わせてサンプルコードを初出に比べて一部修正してあります。
6/2:nullで例外を返すサンプルがnullを返さなくなっていたので修正

LINQに実装されている拡張メソッドの一つにSelectMany
というものがあり作用としては、Selectと同じ作用+配列の平坦化を行なってくれるという活用しどころの多い拡張メソッドの一つです。

具体例としては以下の様なテストコードのような動作をします。
(Stringの配列の配列を平坦化してstringの配列にしてくれます。)
※テストコードはMSTest+Chainning Assertionを利用

        [TestMethod]
        public void TestMethod1()
        {
            var values = new[] { new string[] { "a", "b","c"},
                                 new string[] { "d" }, new string[] { "e" } };

            values.SelectMany(x => x).Is("a","b","c","d","e");               
        }

こっからが本題なのですがそのSelectManyの中のラムダでnullを返すとNullReferenceExceptionが発行されます。

先ほどの例をベースに配列の中に”b”が含まれている時はnullを返す実装例を示します。
※テストコード中では例外が投げられることを期待しているためcatch句に入ったら成功としています。

        [TestMethod]
        public void TestMethod2()
        {
            var values = new[] { new string[] { "a", "b","c"},
                                 new string[] { "d" }, new string[] { "e" } };

            try
            {
                //SelectManyでnullをリターンすると落ちるはず。
                var outputValues = values.SelectMany(x => x.Contains("b") ? null : x).ToArray();
            }
            catch (Exception ex)
            {
                Assert.IsTrue(true);   
            }

        }

上記のような実装をした時、nullがreturnされるタイミングでNullReferenceExceptionが発行されます。

ので、上記のような要件の時は、以下のようにそもそも対象外のものを事前に除外しておくか、

        [TestMethod]
        public void TestMethod3()
        {
            var values = new[] { new string[] { "a", "b","c"},
                                 new string[] { "d" }, new string[] { "e" } };

            values.Where(x => !x.Contains("b")).SelectMany(x => x).Is( "d", "e" );
        }

以下のようにnew string[]{}を返すかする必要があります。

        [TestMethod]
        public void TestMethod4()
        {
            var values = new[] { new string[] { "a", "b","c"},
                                 new string[] { "d" }, new string[] { "e" } };

            values.SelectMany(x => x.Contains("b") ? Enumerable.Empty<string>():x).Is( "d", "e");
        }

まぁどちらかといえば、可能なら事前に除外しておくのが正しいと思います。

Share

疲れているとこんな実装をしてしまってはまる

今日は疲れていたようで以下のようなクソみたいな間違いに気づかないで30分くらい費やしてしまいました。

public class clsssName
{
    private hoge _member = new hoge();
    public className()
    {
           hoge  _member = new hoge();
           _member.property = value;
   }
}

何にはまったか分かりますか?
メンバとして宣言されている名前と同じ名前でローカル変数を宣言してしまったという、ただそれだけの事なのですが、疲れていると30分ほど気づかないで、あーでもない、こーでもないとやってしまうのです。

というわけで、ちゃんと疲れを翌日に残さないようにしましょうという教訓でした。

Share

NPOIを使ってみた

JAVAのPOIが移植されたNPOIというものがあるということを、下記のブログで知った。

C#やVBでExcelを出力するために、POIの.NET版が無いか探してみたら、NPOIというものを見つけたので試してみます!!

NPOI入門してみた – かずきのBlog@Hatena

そこで、早速使ってみたので、気をつける必要がある点だけ記述すると、安易にCreateCellStyleやCreateFontやCreateDataFormatをしていると、上限にすぐ達してしまい例外が発行されるので(5000個くらい?)全く同じものは再利用しましょうという事くらい。

ExcelCreator等に比べてもファイルの作成速度が著しく劣るという事もなく、実用できるレベルだと思う。

リファレンスとしては、上記ブログの記事中にあったJAVAのPOIについて記述されているページが役に立った。

Share

要素の数と内容が決まっている配列を作成する時、より変更に強いコードとは?

下記のような、二つのコードがあるとする。

ケース1:配列を宣言してから代入する。

[cs]
var sample = new string[3];
sample[0] = “abc”;
sample[1] = “def”;
sample[2] = “ghi”;
[/cs]

ケース2:宣言時に要素も設定する。

[cs]
var sample = new string[]
{
“abc”
,”def”
,”ghi”
};[/cs]

要素の数と内容が決まっている配列を作成する時、より変更に強いコードはケース2である。

なぜかというと、ケース1の場合は、要素の数に変更があった時、代入だけ増やして、要素数の変更を忘れる可能性があるから。

Share

Formを閉じる時だけValidatingイベント内での検証をやめる方法

フォームに貼り付けたコントロールのValidatingイベントハンドラ内で

if(!this.CauseValidation && this.ActiveControl == 元のコントロール)
{
return;
}
if (!this.ActiveControl.CausesValidation)
{
return;
}

とする。

×ボタンで閉じる時は、元のコントロールがアクティブなままなので、ActiveControlが元のコントロールだったら抜ける。

本来、ActiveControlのCauseValidationがfalseの時は、Validatingイベントが発生しないはずなので、Validatingイベント内で、ActiveControlのCauseValidationがfalseである事を確認したらreturnで抜ける。

カスタムコントロールの実装時には、OnValidatingをオーバライドして、base.OnVvalidating()を呼ぶまえに

System.Windows.Forms.Control 親コントロール = this.Parent;
while (親コントロール != null)
{
if (親コントロール is System.Windows.Forms.Form)
{
if (親コントロール.CausesValidation)
{
break;
}
if ((親コントロール as System.Windows.Forms.Form).ActiveControl == this)
{
return;
}
if (!(親コントロール as System.Windows.Forms.Form).ActiveControl.CausesValidation)
{
return;
}
}
親コントロール= 親コントロール.Parent;
}

とする。

基本的には、フォームに貼り付けた場合と同じで、追加された実装はFormコントロールを見つけるまで親を遡る事。

Share

アセンブリから特定の型を継承したクラスのメタデータを取得する方法

アセンブリから特定の型を継承したクラスのメタデータを取得したい時は、System.Type型のIsSubclassOfメソッドを利用します。

以下のように使います。

Assembly assemblyObject = Assembly.LoadFile(対象ファイル名);
foreach(System.Type targetType in assemblyObject.GetTypes().Where(x => x.IsSubclassOf(typeof(継承元のクラス))).ToList())
{
Console.WriteLine(targetType.Name);
}
Share

アセンブリから特定のインタフェースを実装したクラスのメタデータを取得する方法

アセンブリから特定のインタフェースを実装したクラスのメタデータを取得したい時は、System.Type型のGetInterfaceメソッドを利用します。

Assembly assemblyObject = Assembly.LoadFile(対象ファイル名);
foreach (System.Type targetType in assemblyObject.GetTypes().Where(x => x.GetInterface("インタフェース名") != null).ToList())
{
Console.WriteLine(targetType.Name);
}
Share

Generic型を動的に生成する方法

WCFを利用したサービスのバックグラウンドで動いてもらうフレームワークを作成するときに必要だったので調べてみると、System.Type型のメソッドにMakeGenricTypeという、そのままのメソッドがあってこれで作成できる事がわかりました。

そこで、ここではList<int>型を作成し、1から10までの値をListに登録するサンプルコードを紹介したいと思います。

普段の実装でこのようなコメントを書くことはありませんがコメントで解説します。

    //List<>の<>内に入れる型のメタデータの配列を生成します。
    //今回はList<int>型を作成したいので、int型のメタデータを生成します。
    System.Type[] genericsTypeArguments = { typeof(int) };

    //List<>のメタデータのMakeGenricTypeメソッドに先ほど作成した
    //int型のメタデータを渡すことでList<int>型のメタデータを作成できます。
    //実は、これを知るまで<>という中に何も入れない書き方ができる事を知りませんでした。
    System.Type integerListType = typeof(List<>).MakeGenericType(genericsTypeArguments);

    //後は通常のメタデータからインスタンスを生成するように
    //CreateInstanceでインスタンスを生成します。
    var integerList = Activator.CreateInstance(integerListType);

    //integerListはobject型であり、動的に作成するというシナリオ上、
    //何のListになったのかわからないので、
    //ListのAddメソッドの情報を取得します。
    var addMethodInfo = integerListType.GetMethod(&quot;Add&quot;);

    //addメソッドを利用するサンプルとして便宜的に1から10までの数と登録します。
    foreach (var value in Enumerable.Range(1,10))
    {
        //Addメソッドの情報からInvokeメソッドを利用して、
        //Addメソッドを実行し、与えられた値を設定します。
        addMethodInfo.Invoke(integerList, new object[] { value });
    }

以上が、ジェネリック型を動的に生成する方法です。

型の配列の要素を現在のジェネリック型定義の型パラメータで置き換え、結果の構築型を表す Type オブジェクトを返します。

Type.MakeGenericType メソッド (System)

Share

凄く便利で分かりやすいのに、見た目だけで保守派に嫌われそうな??演算子

?? 演算子 (C# リファレンス)

?? 演算子は、左側のオペランドが null 値でない場合にはこのオペランドを返し、null 値である場合には右側のオペランドを返します。

?? 演算子 (C#)

??演算子は下記、サンプルのようなコードの場合、aがnullなら0を返す。という動作をします。

int? a = null;
Console.WriteLine(a ?? 0);

もちろん、二つの値だけではなく、以下のような使い方もできます。

左から右の順に初めてnullでは無い値を返します。

下の場合cが初めてnullでは無い値なのでcの値である2を返します。

int? a = null;
int? b = null;
int? c = 2;
int? d = null;
Console.WriteLine(a ?? b ?? c ?? d ?? 0);

新しい演算子なので、当たり前ですが見慣れないので、三項演算子に並んで保守派に忌避されそうな演算子ですね。

ほんの30秒だけMSDNの該当ページを読めば分かる処理内容で分かりやすく読みやすくなるのですが・・・。

Share
カテゴリー: C#

パズル:nまでの整数を二つの組に分けて合計した結果が同じ時のnについて見つけた法則の実証をバイナリサーチで

なんでそうなのかをコンピュータによる実証以外に証明する事ができないので、ドラクエで言うと、まだ冒険の書を作ったところ。

いや、冒険の書すら作れていないのか。

nまでの整数を二つの組に分けて合計した結果が同じ時のnについて見つけた法則 – NAL-6295の舌先三寸

最近、続けている話題ですが、とりあえず見つけた法則が正しいのかどうかを実証するために法則を見つける前のコードをPCで実行させてみたら、さすがに死ぬほど時間がかかって800000に辿りつくまでですら一晩かかったので、バイナリサーチで書き直してみた。

配列なら、Array.BinarySearchがあるのだけれども・・・。

バイナリサーチにしたら、intの幅に関しては、1時間20分くらいで、戻ってくるようになった。

アルゴリズム一つでぜんぜん違うという事を改めて(今更ながら)実感。

public void nまでの整数を二つの組に分けて合計した結果が同じだった数字リスト実証()
{
Func<int, bool> 奇数が奇数個ある = x => ((x / 2) + (x % 2)) % 2 != 0;
//Enumerable.Select().Sum()は使わない方向で
Func<long, long, long> xからyまでの計 = (x, y) => ((x + y) * ((y - x + 1) / 2)) + (((y + x) / 2) * ((y - x + 1) % 2));
Func<int, long,long,long, bool> nまでの数字の合計の半分とnを二つの組にした合計が同じものがある = null;
nまでの数字の合計の半分とnを二つの組にした合計が同じものがある = (n, 総和の半分, 始点, 終点) =>
{
long 探索点 = (終点 + 始点) / 2;
long 総和 = xからyまでの計(探索点, (long)n);
if (総和 == 総和の半分)
{
return true;
}
if (始点 == 終点)
{
return false;
}
if (総和 > 総和の半分)
{
始点 = 探索点;
}
else
{
終点 = 探索点;
}
if (始点 + 1 == 終点)
{
if (総和 > 総和の半分)
{
始点++;
}
else
{
終点--;
}
}
return nまでの数字の合計の半分とnを二つの組にした合計が同じものがある(n, 総和の半分, 始点, 終点);
};
var 該当するnのリスト = Enumerable
.Range(2, int.MaxValue - 2)
.Where(n => !奇数が奇数個ある(n) && nまでの数字の合計の半分とnを二つの組にした合計が同じものがある(n, xからyまでの計(1, n) / 2, 1, n))
.Select(n => n);
foreach (var n in 該当するnのリスト)
{
Console.WriteLine(n);
}
}
Share
カテゴリー: C#