作成日:2022年7月1日
LINQ、便利ですよね。
VB.net、C#でコレクション操作をするのにLINQ無しではやってられないほどです。
便利なLINQですが、
複雑な業務でシステムを作っていると、既存のLINQクエリメソッドだけでは賄いきれないケースもあります。
僕が業務の中で使用している自作のLINQカスタムメソッドをまとめてみました。
※LINQの拡張については「MoreLinq」「ExtraLinq」等が有名ですので、こちらも参考にしてみるとさらに面白いカスタムLINQが作れるかもですね。
LINQのメソッドはIEnumerable<T>で定義されているので、IEnumerableの静的メソッドを定義すれば、
簡単に自作のLINQクエリメソッドが実装できます。
例として、Whereを自作する場合の実装方法を紹介します。
コレクションをforeachで回して、該当レコードだった場合、
イテレータ(yield)を使ってそのレコードを返します。
イテレータを使うことで遅延評価で実行可能となります。例-自作Where
public static class EnumerableEx
{
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> f)
{
foreach (var item in source)
{
if (f(item)) yield return item;
}
}
}
基本的ですがよく使います。
public static IEnumerable<T> ExcludeNullOrDefault<T>(this IEnumerable<T> source)
{
foreach (var item in source)
{
if (item == null || item.Equals(default(T))) continue;
yield return item;
}
}
順番に取り出してKeyValuePair等の辞書を作成します。
public static IEnumerable<KeyValuePair<int,T>> WhereWithIndex<T>(this IEnumerable<T> source, Func<T, bool> f)
{
int index = -1;
foreach (var item in source)
{
checked { index++; }//indexのオーバーフローを補足
if (f(item)) yield return new KeyValuePair<int, T>(index, item);
}
}
一番高いレコードを探す時点ですべての要素にアクセスするので、ここでは遅延評価に対応する必要はありません。
public static IEnumerable<TSource> MaxBy<TKey,TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
where TKey:IComparable<TKey>,IEquatable<TKey>
{
//最大値を取得
var max = source.Max(keySelector);
//最大値のレコードを全取得
return source.Where(x=>keySelector(x).Equals(max));
}
LINQの自作というより、GroupByの処理を共通化するためのメソッドです。
timestampを持っているクラスのコレクションを年度ごとでまとめることが可能です。
業務システムでは帳票データを年度ごとで集計するようなケースが多いので載せました。
public static IEnumerable<IGrouping<int,T>> YearGroupBy<T>(this IEnumerable<T> source, Func<T, DateTime> keyselector,(int month,int day) reference)
{
return source.GroupBy(x =>
{
var date = keyselector(x);
//基準月日を超得ていない場合、年度はdate-1になる。
if (date.Day >= reference.day && date.Month >= reference.month)
{
return date.Year;
}
else
{
return date.Year - 1;
}
});
}
AutoMaapperのIEnumerable版。
例外処理はだいぶ省略してます。
public static IEnumerable<TTarget> AutoMap<TSource,TTarget>(this IEnumerable<TSource> source)
where TTarget:new() where TSource:class
{
foreach (var item in source)
{
var targetObj = new TTarget();
foreach (var prop in typeof(TSource).GetProperties())
{
var targetProp =typeof(TTarget).GetProperty(prop.Name);
//プロパティが存在しないor型が一致しない場合は処理をスキップ
if (targetProp == null || targetProp.GetType()!=prop.GetType()) continue;
targetProp.SetValue(targetObj, prop.GetValue(item));
}
yield return targetObj;
}
}
随時更新中。