読者です 読者をやめる 読者になる 読者になる

タプルを分解して渡す

C# Scala

Scala ではタプルをパターンマッチで分解してくれるので、タプルが非常に使いやすいです。
しかし、C# にはパターンマッチがないので、いちいち Item1 やら _1 やらでアクセスする必要があります。
例えば、

var strs = new[] { "aaa", "bbb", "ccc" };
var idxs = new[] { 0, 1, 2 };
var hoge = strs.Zip(idxs)
               .Select(str_i => string.Format("{1}:{0}", str_i._1, str_i._2));

こんな感じのコードは面倒ですし、読みやすいとはいえません (Zip やその結果のタプルは独自のものを使ってます)。
そこで、こんな感じのラッパーを書きました。

public static IEnumerable<TResult> Select<T1, T2, TResult>(this IEnumerable<Tpl<T1, T2>> self, Func<T1, T2, TResult> selector)
{
    return self.Select(t => selector(t._1, t._2));
}

これを使うと、

var strs = new[] { "aaa", "bbb", "ccc" };
var idxs = new[] { 0, 1, 2 };
var hoge = strs.Zip(idxs)
               .Select((str, i) => string.Format("{0}:{1}", i, str));

このように、タプルの Item1 やら _1 やらを明示的に呼び出す必要がなくなり、より適切な名前をつけることが出来ます。
あとはいつも通りに T4 Template なのですが・・・C#3.5 では残念なことに Func が 4 引数版までしかありません。
なので、これも T4 Template で作ってしまいます。

<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" language="C#v3.5" debug="true" hostSpecific="true" #>
<#@ output extension=".cs" encoding="UTF-8" #>
<#@ Assembly Name="System.dll" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ Assembly Name="System.Windows.Forms.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#
for (int i = 5; i <= 16; i++)
{
    var range = Enumerable.Range(1, i);
    var typeParam = string.Join(", ", range.Select(j => "T" + j).ToArray());
    var funcParam = string.Join(", ", range.Select(j => string.Format("T{0} arg{0}", j)).ToArray());

    var docComment4Func = 
@"/// <summary>
/// {0}個のパラメーターを受け取って TResult パラメーターに指定された型の値を返すメソッドをカプセル化します。
/// </summary>";
    WriteLine(docComment4Func, i);
    WriteLine("public delegate TResult Func<{0}, TResult>({1});", typeParam, funcParam);
    WriteLine("");

    var docComment4Action =
@"/// <summary>
/// {0}個のパラメーターを受け取り、戻り値を持たないメソッドをカプセル化します。
/// </summary>";
    WriteLine(docComment4Action, i);
    WriteLine("public delegate void Action<{0}>({1});", typeParam, funcParam);
    WriteLine("");
}
#>

Func のついでに Action も作ってしまいました。
そして、本体。念のため 10 要素のタプルまで対応していますが、そのために生成されるコードは 4000 行をこえます。
10 要素分もいらないよ!って人は、n の数を変えてください。

<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" language="C#v3.5" debug="true" hostSpecific="true" #>
<#@ output extension=".cs" encoding="UTF-8" #>
<#@ Assembly Name="System.dll" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ Assembly Name="System.Windows.Forms.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#
int n = 10;
#>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CommonsSharp.Lang
{
    partial class IEnumerableExtension
    {
<#
for (int i = 2; i <= n; i++) {
    var range = Enumerable.Range(1, i);
    var types = range.Select(j => "T" + j).ToArray();
    
    var typeParam = string.Join(", ", types);
    var tupleExpand = string.Join(", ", range.Select(j => "t._" + j).ToArray());
#>
        public static IEnumerable<TResult> Select<<#= typeParam #>, TResult>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TResult> selector)
        {
            return self.Select(t => selector(<#= tupleExpand #>));
        }

        public static IEnumerable<TResult> Select<<#= typeParam #>, TResult>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, int, TResult> selector)
        {
            return self.Select((t, i) => selector(<#= tupleExpand #>, i));
        }

        public static IEnumerable<TResult> SelectMany<<#= typeParam #>, TResult>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, IEnumerable<TResult>> selector)
        {
            return self.SelectMany(t => selector(<#= tupleExpand #>));
        }

        public static IEnumerable<TResult> SelectMany<<#= typeParam #>, TResult>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, int, IEnumerable<TResult>> selector)
        {
            return self.SelectMany((t, i) => selector(<#= tupleExpand #>, i));
        }

        public static IEnumerable<TResult> SelectMany<<#= typeParam #>, TCollection, TResult>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, IEnumerable<TCollection>> collectionSelector, Func<<#= typeParam #>, TCollection, TResult> resultSelector)
        {
            return self.SelectMany(t => collectionSelector(<#= tupleExpand #>), (t, c) => resultSelector(<#= tupleExpand #>, c));
        }

        public static TAccumulate Aggregate<<#= typeParam #>, TAccumulate>(this IEnumerable<Tpl<<#= typeParam #>>> self, TAccumulate seed, Func<TAccumulate, <#= typeParam #>, TAccumulate> func)
        {
            return self.Aggregate(seed, (acc, t) => func(acc, <#= tupleExpand #>));
        }

        public static TResult Aggregate<<#= typeParam #>, TAccumulate, TResult>(this IEnumerable<Tpl<<#= typeParam #>>> self, TAccumulate seed, Func<TAccumulate, <#= typeParam #>, TAccumulate> func, Func<TAccumulate, TResult> resultSelector)
        {
            return self.Aggregate(seed, (acc, t) => func(acc, <#= tupleExpand #>), resultSelector);
        }

        public static bool Any<<#= typeParam #>>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, bool> predicate)
        {
            return self.Any(t => predicate(<#= tupleExpand #>));
        }

<# foreach (var methodName in new[] { "First", "Last", "Single" }) { #>

        public static Tpl<<#= typeParam #>> <#= methodName #><<#= typeParam #>>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, bool> predicate)
        {
            return self.<#= methodName #>(t => predicate(<#= tupleExpand #>));
        }

        public static Tpl<<#= typeParam #>> <#= methodName #>OrDefault<<#= typeParam #>>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, bool> predicate)
        {
            return self.<#= methodName #>OrDefault(t => predicate(<#= tupleExpand #>));
        }

<# } #>
<# foreach (var methodName in new[] { "Where", "SkipWhile", "TakeWhile" }) { #>

        public static IEnumerable<Tpl<<#= typeParam #>>> <#= methodName #><<#= typeParam #>>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, bool> predicate)
        {
            return self.<#= methodName #>(t => predicate(<#= tupleExpand #>));
        }

        public static IEnumerable<Tpl<<#= typeParam #>>> <#= methodName #><<#= typeParam #>>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, int, bool> predicate)
        {
            return self.<#= methodName #>((t, i) => predicate(<#= tupleExpand #>, i));
        }

<# } #>

        public static IEnumerable<IGrouping<TKey, Tpl<<#= typeParam #>>>> GroupBy<<#= typeParam #>, TKey>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector)
        {
            return self.GroupBy(t => keySelector(<#= tupleExpand #>));
        }

        public static IEnumerable<IGrouping<TKey, Tpl<<#= typeParam #>>>> GroupBy<<#= typeParam #>, TKey>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, IEqualityComparer<TKey> comparer)
        {
            return self.GroupBy(t => keySelector(<#= tupleExpand #>), comparer);
        }

        public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<<#= typeParam #>, TKey, TElement>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, Func<<#= typeParam #>, TElement> elementSelector)
        {
            return self.GroupBy(t => keySelector(<#= tupleExpand #>), t => elementSelector(<#= tupleExpand #>));
        }

        public static IEnumerable<TResult> GroupBy<<#= typeParam #>, TKey, TResult>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, Func<TKey, IEnumerable<Tpl<<#= typeParam #>>>, TResult> resultSelector)
        {
            return self.GroupBy(t => keySelector(<#= tupleExpand #>), resultSelector);
        }

        public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<<#= typeParam #>, TKey, TElement>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, Func<<#= typeParam #>, TElement> elementSelector, IEqualityComparer<TKey> comparer)
        {
            return self.GroupBy(t => keySelector(<#= tupleExpand #>), t => elementSelector(<#= tupleExpand #>), comparer);
        }

        public static IEnumerable<TResult> GroupBy<<#= typeParam #>, TKey, TResult>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, Func<TKey, IEnumerable<Tpl<<#= typeParam #>>>, TResult> resultSelector, IEqualityComparer<TKey> comparer)
        {
            return self.GroupBy(t => keySelector(<#= tupleExpand #>), resultSelector, comparer);
        }

        public static IEnumerable<TResult> GroupBy<<#= typeParam #>, TKey, TElement, TResult>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, Func<<#= typeParam #>, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector)
        {
            return self.GroupBy(t => keySelector(<#= tupleExpand #>), t => elementSelector(<#= tupleExpand #>), resultSelector);
        }

        public static IEnumerable<TResult> GroupBy<<#= typeParam #>, TKey, TElement, TResult>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, Func<<#= typeParam #>, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector, IEqualityComparer<TKey> comparer)
        {
            return self.GroupBy(t => keySelector(<#= tupleExpand #>), t => elementSelector(<#= tupleExpand #>), resultSelector, comparer);
        }

<#
foreach (var methodName in new[] { "Average", "Max", "Min", "Sum" }) {
    foreach (var argRetType in new[] { "decimal", "double", "float", "int:double", "long:double", "decimal?", "double?", "float?", "int?:double?", "long?:double?" }) {
        var argType = argRetType.Split(':')[0];
        var retType = argRetType.Split(':')[methodName == "Average" && argRetType.Contains(":") ? 1 : 0];
#>
        public static <#= retType #> <#= methodName #><<#= typeParam #>>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, <#= argType #>> selector)
        {
            return self.<#= methodName #>(t => selector(<#= tupleExpand #>));
        }

<#
     }
     if (methodName == "Average" || methodName == "Sum")
         continue;
#>
        public static TResult <#= methodName #><<#= typeParam #>, TResult>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TResult> selector)
        {
            return self.<#= methodName #>(t => selector(<#= tupleExpand #>));
        }
<# } #>

        public static int Count<<#= typeParam #>>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, bool> predicate)
        {
            return self.Count(t => predicate(<#= tupleExpand #>));
        }

        public static long LongCount<<#= typeParam #>>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, bool> predicate)
        {
            return self.LongCount(t => predicate(<#= tupleExpand #>));
        }

<# foreach (var methodName in new[] { "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending" }) { #>

        public static IOrderedEnumerable<Tpl<<#= typeParam #>>> <#= methodName #><<#= typeParam #>, TKey>(this <#= methodName.StartsWith("Then") ? "IOrderedEnumerable" : "IEnumerable" #><Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector)
        {
            return self.<#= methodName #>(t => keySelector(<#= tupleExpand #>));
        }

        public static IOrderedEnumerable<Tpl<<#= typeParam #>>> <#= methodName #><<#= typeParam #>, TKey>(this <#= methodName.StartsWith("Then") ? "IOrderedEnumerable" : "IEnumerable" #><Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, IComparer<TKey> comparer)
        {
            return self.<#= methodName #>(t => keySelector(<#= tupleExpand #>), comparer);
        }

<# } #>

        public static Dictionary<TKey, Tpl<<#= typeParam #>>> ToDictionary<<#= typeParam #>, TKey>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector)
        {
            return self.ToDictionary(t => keySelector(<#= tupleExpand #>));
        }

        public static Dictionary<TKey, Tpl<<#= typeParam #>>> ToDictionary<<#= typeParam #>, TKey>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, IEqualityComparer<TKey> comparer)
        {
            return self.ToDictionary(t => keySelector(<#= tupleExpand #>), comparer);
        }

        public static Dictionary<TKey, TElement> ToDictionary<<#= typeParam #>, TKey, TElement>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, Func<<#= typeParam #>, TElement> elementSelector)
        {
            return self.ToDictionary(t => keySelector(<#= tupleExpand #>), t => elementSelector(<#= tupleExpand #>));
        }

        public static Dictionary<TKey, TElement> ToDictionary<<#= typeParam #>, TKey, TElement>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, Func<<#= typeParam #>, TElement> elementSelector, IEqualityComparer<TKey> comparer)
        {
            return self.ToDictionary(t => keySelector(<#= tupleExpand #>), t => elementSelector(<#= tupleExpand #>), comparer);
        }

        public static ILookup<TKey, Tpl<<#= typeParam #>>> ToLookup<<#= typeParam #>, TKey>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector)
        {
            return self.ToLookup(t => keySelector(<#= tupleExpand #>));
        }

        public static ILookup<TKey, Tpl<<#= typeParam #>>> ToLookup<<#= typeParam #>, TKey>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, IEqualityComparer<TKey> comparer)
        {
            return self.ToLookup(t => keySelector(<#= tupleExpand #>), comparer);
        }

        public static ILookup<TKey, TElement> ToLookup<<#= typeParam #>, TKey, TElement>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, Func<<#= typeParam #>, TElement> elementSelector)
        {
            return self.ToLookup(t => keySelector(<#= tupleExpand #>), t => elementSelector(<#= tupleExpand #>));
        }

        public static ILookup<TKey, TElement> ToLookup<<#= typeParam #>, TKey, TElement>(this IEnumerable<Tpl<<#= typeParam #>>> self, Func<<#= typeParam #>, TKey> keySelector, Func<<#= typeParam #>, TElement> elementSelector, IEqualityComparer<TKey> comparer)
        {
            return self.ToLookup(t => keySelector(<#= tupleExpand #>), t => elementSelector(<#= tupleExpand #>), comparer);
        }

<# } #>
    }
}

面倒なので Join 系は実装していません。
あと微妙に規則性があるので、もうちょっと短く出来そうです。