デリゲート型のFuncではまる

Gakuです。
C#でデリゲート型という便利な型があるのですが、その中でFunc型を使った時にはまったのでその備忘録を記述します。
Funcとは引数あり戻り値ありで、関数を格納できる型です。
こんな感じで記述できます。

static void Main(string[] args)
{
    //関数を定義 (x, y) => x + y;で以下の関数を格納している感じ。
    //private string f(string x,string y)
    //{
    //  return x+y;
    //}
    Func<string, string, string> func = (x, y) => x + y;

    //実行
    Console.WriteLine(func("名前:","gaku"));
}

出力結果は「名前:gaku」と出力されるはずです。うん。すごく便利。
で、問題のはまったコードは以下のような感じです。

static void Main(string[] args)
{
    var funcs = new List<Func<string, string>>();
    List<string> items = new List<string>() { "gakuさん","gaku子","gaku美さん" };
    for(int i = 0; i < items.Count(); i++)
    {
        funcs.Add(x => x + items[i]);
    }

    foreach(var func in funcs)
    {
        Console.WriteLine(func("名前:"));
    }
}

出力結果として期待するのはこんな感じだと思います。

名前:gakuさん
名前:gaku子
名前:gaku美さん

ですがこのコードはArgumentOutOfRangeException例外をはいて落ちてしまいます。
なんでなんや。。。なんでやあぁああ!ってなると思いますが、考えてみたら至極簡単でした。

/// <summary>
/// 解説:
/// ①:関数をfuncsへ登録します。まだ格納した関数は実行されていません。
/// ②:この時点でiは3が格納されています。
/// ③:ここでようやく格納した関数を実行します。iは3です。
///  配列はitem[0]~item[2]までしか作成されていません。
///  落ちるしかないっすわw
/// </summary>
static void Main(string[] args)
{
    var funcs = new List<Func<string, string>>();
    List<string> items = new List<string>() { "gakuさん", "gaku子", "gaku美さん" };
    for (int i = 0; i < items.Count(); i++)
    {
        //①
        funcs.Add(x => x + items[i]);
    }
    //②
    foreach (var func in funcs)
    {
        //③
        Console.WriteLine(func("名前:"));
    }
}

こんな感じです。関数としてfuncsに登録する際、iは値を登録するのではなくポインタを登録しているため、実行時に3となっているiを参照してしまい落ちてるというわけですね。
さてどうするものかと。以下のような形で解決できます。

static void Main(string[] args)
{
    var funcs = new List<Func<string, string>>();
    List<string> items = new List<string>() { "gakuさん", "gaku子", "gaku美さん" };
    for (int i = 0; i < items.Count(); i++)
    {
        //ここが重要!新たにメモリ上に値を保持する
        var number = i;
        funcs.Add(x => x + items[number]);
    }
    foreach (var func in funcs)
    {
        Console.WriteLine(func("名前:"));
    }
}

「var number = i;」でforのループの回数分メモリ上にiの値をコピーした領域を作成させてあげるわけです。なるほどね~。
出力結果を見ると正常に出力されているはずです。

名前:gakuさん
名前:gaku子
名前:gaku美さん

こういうところにハードウェアの知識とか絶対必要だよな~っとすごく思います。
正直、「参照型・値型の解説をしてください!」とエンジニアに聞いたら何人の人がちゃんと答えられるだろうか。
いや、さすがに答えられない人はいないと思うけど、funcとか使いだすと途端に初歩的なことが分からなくなるから気をつけないといけないな~と感じている今日この頃です(´・ω・`)

Linqのメソッド式で動的OrderBy句を実装する[DataTable編]

Gakuです。
昨日の続きになるのですが、DataTableでLinqのOrderBy,ThenByを連結させる方法を掲載します。
Linq to OnjectとLinq to SQLとでは.Net内のコードが違うようでいろいろハマりました。
とりあえずテストデータはこんな感じです。

/// <summary>
/// データ作成(ソートテストのためランダムな値を生成)
/// </summary>
static DataTable CreateDataTable()
{
    DataTable table = new DataTable();
    table.Columns.Add("name");
    table.Columns.Add("age");
    table.Columns.Add("sex");
    table.Columns.Add("comment");

    for(int i = 0; i < 100; i++)
    {
        Random randomNumber = new Random(i);

        DataRow row = table.NewRow();
        row["name"] = "gaku" + randomNumber.Next(100) %2;
        row["age"] = i%2;
        row["sex"] = "男";
        row["comment"] = "うへへ" + (100-i).ToString();
        table.Rows.Add(row);
    }

    return table;
}

次にメインメソッドはこんな感じです。

static void Main(string[] args)
{
    //DataTable取得
    DataTable testDataTable = CreateDataTable();
    //通常LinqでのOrderBy,ThenBy
    var test = testDataTable.AsEnumerable().OrderBy(x => x.Field<string>("name")).ThenBy(x => x.Field<string>("age")).ThenBy(x => x.Field<string>("comment")).CopyToDataTable();
    //可変OrderBy,ThenBy
    var sortDataTable = testDataTable.AsEnumerable().ConcatenationSortForDataTable(new List<string>() { "name","age", "comment" }).CopyToDataTable();
}

で、昨日と同じでUtilClassを作成し以下のように記述します。

static class UtilClass
{
    /// <summary>
    /// 連結ソート (DataTable版)
    /// </summary>
    /// <param name="query"></param>
    /// <param name="sortItemList"></param>
    /// <returns></returns>
    public static OrderedEnumerableRowCollection<DataRow> ConcatenationSortForDataTable(this EnumerableRowCollection<DataRow> query, List<string> sortItemList)
    {
        OrderedEnumerableRowCollection<DataRow> orderedQuery = null;
        int index = 0;
        foreach (var item in sortItemList)
        {
            Func<DataRow, string> sortFunc = (x) => x.Field<string>(item);
            if (index == 0)
                orderedQuery = query.OrderBy(sortFunc);
            else
                orderedQuery = orderedQuery.ThenBy(sortFunc);
            index++;
        }

        return orderedQuery;
    }
}

ふぅ~。すっきり。
ソート項目が複数になると何が正しいのかわからなくなって、混乱しましたがようやく完成しました。
しかし、「なんでC#erの人は.Net内部とかC#のことをこんな知っているんだろう」とずっと疑問に思っていましたが、本日の実装でひたすら.NETライブラリの中身を閲覧していたことを考えると、必然的に強くなるんだろうな~と感じます。やっぱC#erは変態っすわ(´・ω・`)

Linqのメソッド式で動的OrderBy句を実装する

C#で強力なLinq。
未だに「Linq」単体でGoogle検索したら、日本の福岡県福岡市を拠点に活動するローカルアイドルグループLinQが出てくる悲しい世の中。
絶対C#erの人たちが検索ランク操作してるだろうと思う今日この頃です。

さて、最近仕事で可変につぐ可変なソースコード改修をやっているのですが、linqのメソッド式がどうしても可変に対応してくれていなくイライラしております。
具体的にはこんな感じ。

//Dataを仮にこんな感じで生成する
class Data
{
    /// <summary>
    /// 100個のデータを生成
    /// </summary>
    /// <returns></returns>
    public IEnumerable<DataClass> GetData()
    {
        for(int i = 0; i < 100; i++)
        {
            Guid g = System.Guid.NewGuid();
            Guid h = System.Guid.NewGuid();
            Random cRandom = new System.Random(i);
            yield return new DataClass
            {
                Name = g.ToString("N").Substring(0, 8),     //名前と仮定
                Age = cRandom.Next(100),
                Sex = cRandom.Next(3),
                Pref = h.ToString("N").Substring(0, 8)      //県名と仮定
            };
        }
    }
}

public class DataClass
{
    public string Name { get; set; }
    public int Age { get; set; }
    public int Sex { get; set; }
    public string Pref { get; set; }
}

でメインでこんな感じでlinq加工する。

Data data = new Data();
var listData = data.GetData();

//普通のOrderByLinq式
var test = listData.OrderBy(x => x.Sex).ThenBy(x=>x.Pref);

なんだThenByって!これじゃあ、可変な感じでソートできねぇじゃねえかぁ!
本来ならこうしたい

var test = listData.OrderBy(x => new {x.Sex,x.Pref });

これだとICompareを実装してないからダメと怒られる。
まぁ、そうだよな~っと思いつつ、実装したとしても根本的解決にはならなかった。
じゃあどうするかと。はじめ以下のようには考えた。

if(count = 0)
    var test = listData.OrderBy(x => x.Sex);
if(count = 1)
    var test = listData.OrderBy(x => x.Sex).ThenBy(x=>x.Pref);
if(count = 2)
    var test = listData.OrderBy(x => x.Sex).ThenBy(x=>x.Pref).ThenBy(x=>x.Age);
if(count = 3)
    var test = listData.OrderBy(x => x.Sex).ThenBy(x=>x.Pref).ThenBy(x=>x.Age).ThenBy(x=>x.Name);

まごうことなき糞コード。
仮に100項目あるならこれが永遠と続いていく。まぁ、10項目ぐらいだったら。。。ぎり許せるレベルだけど、許せない。

じゃあ、どうするかと。帰宅してからいろいろ考えて考えて出たコードが以下のような感じ。

//メイン
//動的OrderByLinq式
var test2 = listData.ConcatenationSort(new List<string> { "Sex","Age" });
//utilクラスを作成
public static class LinqUtil
{
    /// <summary>
    /// 連結ソート
    /// </summary>
    public static IEnumerable<T> ConcatenationSort<T>(this IEnumerable<T> query, List<string> sortExpressions)
    {
        int index = 0;
        IOrderedEnumerable<T> orderedQuery = null;
        foreach (var sortExpr in sortExpressions)
        {
            Func<T, object> expression = item => item.GetType()
                         .GetProperty(sortExpr)
                         .GetValue(item, null);
            orderedQuery = (index == 0) ? query.OrderBy(expression) : orderedQuery.ThenBy(expression);
            index++;
        }
        query = orderedQuery;
        return query;
    }
}

はぁ~すっきりっす。
これでソート項目100個になっても1万個になってもList<string>部分の値を変化させてあげるだけでソートしてくれます。
こういうコードが速攻出てくる人がC#er兼Linq星人なんだろうなと思います。
GroupByもつまずいたので、これで対応できなければ記事にします。

参考文献(神)
[blogcard url=”http://www.codeproject.com/Articles/280952/Multiple-Column-Sorting-by-Field-Names-Using-Linq”]

高速化のためにBulkInsertを試す

Gakuです。
業務システムのデータが100万行を超えだして速度問題がいろいろ出てきたので、抜本的な改革を入れるべくいろいろ帰宅して勉強してた。
リファクタリングしてぇ~な~って思ってて、テストコードしこしこ書いてたりしたんだけど、テストデータを入れるのもすごく時間がかかる。
いろいろ調べててBulkInsertってのを見つけたので試してみた。

1件もミスなくデータが格納できる状態であれば使用することが可能。
一応コードもGitHubに公開してみた。
[blogcard url=”https://github.com/gaku3601/BulkInsertTest/blob/master/BulkInsertTestProject/Program.cs”]

で、6万件のデータを格納した際の速度はこうなった。
bulk

100倍近い速度が出てる。やばい。
が、実運用しているシステムで何か起こったときデバッグしずらすぎるのでそこでは使えないな~っと思いつつ、強力すぎるのでテストコード書くときには積極的に使っていきたいと思います。
まだまだ手を入れれるところがありすぎるので楽しいと思っている今日この頃(´・ω・`)