SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

ReSharperでLinq変換

ReSharperとは

ReSharperとは、Visual Studio のアドインで下記の機能があります。
・コーディング支援
リファクタリング支援
・ユニット・テスト
・その他、リッチな機能がいろいろ

最新版は、Ver7.1.3となり、VisualStudio2012に対応しています。

一度使うと中毒になり、ReSharper畜となり、無いと不安に・・・・・かも知れません。

Linqへの変換

リファクタリング機能の一つとして、foreachのループ文をクリック一発でLinqに変換する機能が有ります。

変換可能なループ文があると、foreachの下の波線と欄外に黄色いランプが表示されます。



ランプを右クリックすると操作可能なメニューが表示されます。



「Convert into LINQ-Expression」を選択すると、Where()に変換されているのが分かります。


暗号化?

次のコードは、BlackJumboDogの断片なのですが、ReSharperの機能でLingに変換すると、下記のようになりました。

public string Sample_002() {
  var log = "";
  var db = new List<OneRR>();
  foreach (var oneRr in db) {
    if (oneRr.DnsType == 1) {
      if (!oneRr.IsEffective)
        continue;
      if (oneRr.Name.ToUpper() == "ABC") {
        var find = false;
        foreach (var o in db) {
          if (o.Data == oneRr.Data) {
            find = true;
            break;
          }
        }
        if (!find) {
          log="Success data search.";
        }
      }
    }
  }
  return log;
}


public string Sample_002() {
  var log = "";
  var db = new List<OneRR>();
  foreach (var find in from oneRr in db where oneRr.DnsType == 1 
      where oneRr.IsEffective where oneRr.Name.ToUpper() == "ABC" 
      select db.Any(o => o.Data == oneRr.Data) into find 
      where !find select find){
    log="Success data search.";
  }
  return log;
}

リファクタリングというより、暗号化?かもしれませんw

変換ロジック

ReSharperのLinq変換が、どのようなロジックで動作しているのか、少しでも分からないかと考え、Linqの機能ごとに変換の様子を確認してみました。

// 以降のサンプルは、下のクラス変数を対象にしています
private readonly int _list = new { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

1.【判定】All,Any,Containsなど

変換前

public void 条件を満たす要素が含まれているか_Any() {
  var flg=false;
  foreach (var i in _list){
    if (i > 5){
      flg = true;
      break;
    }
  }
}
public void すべての要素が条件を満たしてるか_All() {
  var flg = true;
  foreach (var i in _list) {
    if (i > 5) {
      flg=false;
      break;
    }
  }
}
public void 指定要素が含まれているかどうか_Contains() {
  var flg = false;
  foreach (var i in _list) {
    if (i == 5) {
      flg = true;
      break;
    }
  }
}

変換後

public void 条件を満たす要素が含まれているか_Any() {
  var flg = _list.Any(i => i > 5);
}
public void すべての要素が条件を満たしてるか_All() {
  var flg = _list.All(i => i <= 5);
}
public void 指定要素が含まれているかどうか_Contains() {
  var flg = _list.Any(i => i == 5);
  //Containsの方が、コードの意味はこっち
  //flg = _list.Contains(5);
}

一瞬、うまく変換している!と思うのですが、よく見てみると、AnyとAllの違いは、元のコードがfalseのフラグをtrueにしてループを抜けるか、trueのフラグをfalseにして抜けるかの違いなので、コードの文意を判断しているわけでは無いようです。
その証拠に、文意がContains()だとしても、フラグが具合だけでAnyかAllになっています。

2.【集計】Max,Min,Average,Sum,Countなど

変換前

public void 要素のカウント_count() {
  var count = 0;
  foreach (var i in _list){
    count++;
  }
}
public void 要素の合計_Sum() {
  var sum = 0;
  foreach (var i in _list) {
    sum+=i;
  }
}
public void 最大値_Max() {
  var max = 0;
  foreach (var i in _list) {
    if (max < i) {
      max = i;
    }
  }
}

変換後

public void 要素のカウント_count() {
  var count = _list.Count();
}
public void 要素の合計_Sum() {
  var sum = _list.Sum();
}
public void 最大値_Max() {
  var max = _list.Concat(new[]{0}).Max();
  //これでいいんじゃ無いだろうか・・・
  //max = _list.Max();
}

ちょっとMaxだけが微妙でしたが、大体いい感じです。

3.【複数の要素取得】Where,Distinct,Skip,Takeなど

変換後

public void 条件を満たす要素の抽出_where() {
  var res = new List<int>();
  foreach (var i in _list) {
    if (i > 5) {
      res.Add(i);
    }
  }
}
public void 先頭から3つをスキップ() {
  var res = new List<int>();
   var count = 0;
  foreach (var i in _list) {
    if (count++ < 3) {
       continue;
    }
    res.Add(i);
   }
}
public void 先頭から3つだけを取得() {
  var res = new List<int>();
  var count = 0;
  foreach (var i in _list) {
    if (count++ < 3) {
      break;
    }
    res.Add(i);
  }
}

変換後

public void 条件を満たす要素の抽出_where() {
  var res = _list.Where(i => i > 5).ToList();
}
public void 先頭から3つをスキップ() {
  var count = 0;
  var res = _list.Where(i => count++ >= 3).ToList();
  //これでいいんじゃ無いだろうか・・・
  //res = _list.Skip(3).ToList();
}
public void 先頭から3つだけを取得() {
  var count = 0;
  var res = _list.TakeWhile(i => count++ >= 3).ToList();
  //これでいいんじゃ無いだろうか・・・
  //res = _list.Take(3).ToList();
}

SkipとかTakeになってほしいところも、全部Where、TakeWhileになってしまいます。

4.【単一の要素取得】First,Last,Single,ElementAtなど

変換前

public void 指定インデックの値取得(){
  int c = 0;
  int res;
  foreach (var i in _list) {
    if (5==c++) {
      res = i;
      break;
    }
  }
}
public void 最初の値取得() {
  int res;
  int c = 0;
  foreach (var i in _list) {
    if (c == 0) {
      res = i;
      break;
    }
  }
}
public void 最後の値取得() {
  int res = -1;
  int c = 0;
  foreach (var i in _list) {
    if (c++ == _list.Count() - 1){
      res = i;
      break;
    }
  }
}

変換後

public void 指定インデックの値取得(){
  int c = 0;
  int res;
  foreach (var i in _list.Where(i => 5==c++)){
    res = i;
    break;
  }
  //これでいいんじゃ無いだろうか・・・
  //res = _list.ElementAt(5);
}
public void 最初の値取得() {
  int res;
  int c = 0;
  foreach (var i in _list.Where(i => c == 0)){
    res = i;
    break;
  }
  //これでいいんじゃ無いだろうか・・・
  //res = _list.First();
}
public void 最後の値取得() {
  int res = -1;
  int c = 0;
  foreach (var i in _list.Where(i => c++ == _list.Count() - 1)){
    res = i;
    break;
  }
  //これでいいんじゃ無いだろうか・・・
  //res = _list.Last();
}

こちらは、全滅です。
どうしても、Where+foreachになってしまいます・・・・・

5.【その他】

その他として・・・・・

「集合」     Union(和集合)Except(差集合) Intersect(積集合)
「ソート」    OrderBy(昇順ソート)Reverse(降順ソート)
「グルーピング」 GroupBy(グルーピング)
「結合」     Join(内部結合)Concat(連結)Zip(マージ)

なんとか、上記に変換されるループを作ろうと色々試してみたのですが、殆どwhere/select・Any/Allあたり変換されてしまって、結局うまく行きませんでした。
私のループ分がショボイのか、ReSharperの機能がそういう事なのか・・・・ちょっと不明です。

結論

結論というか、まったく私の勝手なイメージなんですが・・・
なんとなく、殆どの変換はAny,All,Where,selectぐらい解決してしまっているような気がしてきました。そういうイメージで、最初の暗号化?されたLinqを眺めてみると、なるほど・・・って感じもしてきます。

すいません、ほんと、私の勝手な結論です。
根拠は一切ありません。