5349


System.Linq.GroupBy()メソッドについて、Groupingのキー項目を  
動的に指定するにはどうしたらよいのでしょうか。  
対応方法をご存知の方がいらっしゃいましたら、ご教示お願いいたします。

なお、.NET Frameworkは4.5を使用しています。  
Groupingの結果、keyは一意になる場合に限り出力し、  
valueは、常にサマリ値を出力するという制限を設けております。

[code]

    // メイン処理
    void main()
    {
        // 初期化
        groupByList = new List<GroupByEntity>();
    
        for (int i = 0; i < 10; i++)
        {
            groupByList.Add(new GroupByEntity()
            {
                key1 = "key1" + Math.Floor((double)(i / 2)).ToString(),
                key2 = "key2" + Math.Floor((double)(i / 3)).ToString(),
                key3 = "key3" + Math.Floor((double)(i / 4)).ToString(),
                key4 = "key4" + Math.Floor((double)(i / 4) + 2).ToString(),
                key5 = "key5" + Math.Floor((double)(i / 5)).ToString(),
                value1 = 10 + i,
                value2 = 20 + i,
                value3 = 30 + i
            });
        }
    
        outputToConsole(groupByList, "初期値");
    
        // 動的GroupBy
        Type groupByType = typeof(GroupByEntity);
        ParameterExpression groupByParam = Expression.Parameter(groupByType, "m");
    
        MemberExpression key1Member = Expression.MakeMemberAccess(groupByParam,groupByType.GetMember("key1").First());
        MemberExpression key2Member = Expression.MakeMemberAccess(groupByParam,groupByType.GetMember("key2").First());
        MemberExpression key3Member = Expression.MakeMemberAccess(groupByParam,groupByType.GetMember("key3").First());
        MemberExpression key4Member = Expression.MakeMemberAccess(groupByParam,groupByType.GetMember("key4").First());
        MemberExpression key5Member = Expression.MakeMemberAccess(groupByParam,groupByType.GetMember("key5").First());
    
        List<MemberExpression> keyMembers = new List<MemberExpression>();
    
        //---修正前ここから---
        //keyMembers.Add(key1Member);
        //keyMembers.Add(key2Member);
        //
        //// ■■このkeyMembersを、下のgroupingにラムダ式として設定したい（以下のように）■■
        //---修正前ここまで---
        //---修正後ここから---
        if (条件１) keyMembers.Add(key1Member);
        if (条件２) keyMembers.Add(key2Member);
        if (条件３) keyMembers.Add(key3Member);
        if (条件４) keyMembers.Add(key4Member);
        if (条件５) keyMembers.Add(key5Member);
    
        // ■■このkeyMembersを、下のgroupingにラムダ式として設定したい
        // ■■例えば、上記条件１・条件２がtrueとなる場合は、下記groupingと同等になるようにしたい
        //---修正後ここまで---
    
        Expression<Func<GroupByEntity, dynamic>> grouping = (m => new { m.key1, m.key2 });
    
        List<GroupByEntity> list = groupByList
            .GroupBy(grouping.Compile())
            .Select(m => new GroupByEntity()
            {
                key1 = selectUniqueValue(m.Select(val => val.key1)),
                key2 = selectUniqueValue(m.Select(val => val.key2)),
                key3 = selectUniqueValue(m.Select(val => val.key3)),
                key4 = selectUniqueValue(m.Select(val => val.key4)),
                key5 = selectUniqueValue(m.Select(val => val.key5)),
                value1 = m.Sum(val => val.value1),
                value2 = m.Sum(val => val.value2),
                value3 = m.Sum(val => val.value3)
            })
            .ToList();
    
        outputToConsole(list, "結果");
    }
    
    // リスト内の文字列が一意になる場合に限り、その値を戻り値とする
    private string selectUniqueValue(IEnumerable<string> list)
    {
        if (list.Distinct().Count() == 1)
        {
            return list.First();
        }
        else
        {
            return string.Empty;
        }
    }
    
    // コンソールへの出力
    private void outputToConsole(List<GroupByEntity> list, string title)
    {
        Console.WriteLine(string.Format("---{0}---", title));
    
        foreach (GroupByEntity row in list)
        {
            Console.WriteLine(row.createOutputLine());
        }
    
        Console.WriteLine("------------");
    }
    
    // Grouping用属性
    public class GroupByEntity
    {
        public string key1;
        public string key2;
        public string key3;
        public string key4;
        public string key5;
        public int value1;
        public int value2;
        public int value3;
    
        // 出力用文字列作成
        public string createOutputLine()
        {
            List<string> ret = new List<string>();
    
            ret.Add(createOutputValue(key1));
            ret.Add(createOutputValue(key2));
            ret.Add(createOutputValue(key3));
            ret.Add(createOutputValue(key4));
            ret.Add(createOutputValue(key5));
            ret.Add(createOutputValue(value1.ToString()));
            ret.Add(createOutputValue(value2.ToString()));
            ret.Add(createOutputValue(value3.ToString()));
    
            return string.Join(",", ret);
        }
    
        // 出力用文字列整形
        public string createOutputValue(string value)
        {
            return value.PadRight(7);
        }
    }
    
[/code]


CC BY-SA 3.0
2015-01-26T13:30:44.380
5342
2015-01-27T14:07:39.723
2015-01-27T13:50:44.967
7825
7825
question
c# linq
Linq.GroupByにおけるGroupingのキー項目の動的な指定について


> m => new { m.key1, m.key2 }

これは`Expression.MemberInit`で生成できる式ですが、匿名クラスは動的に作成できませんので`Tuple`を返す`Expression`を作成してください。

実際にgrouping相当のExpressionを作るには以下のような手順となります。

[code]

    if (keyMembers.Count == 0)
    {
        // TODO:
    }
    else if (keyMembers.Count == 1)
    {
        var delegateType = typeof(Func<,>).MakeGenericType(groupByType, keyMembers[0].Type);
        grouping = Expression.Lambda(delegateType, keyMembers[0], groupByParam);
    }
    else
    {
        Type genericDefinition;
        switch (keyMembers.Count)
        {
            case 2: genericDefinition = typeof(Tuple<,>); break;
            case 3: genericDefinition = typeof(Tuple<,,>); break;
            case 4: genericDefinition = typeof(Tuple<,,,>); break;
            default: genericDefinition = typeof(Tuple<,,,,>); break;
        }
        var constructed = genericDefinition.MakeGenericType(keyMembers.Select(_ => _.Type).ToArray());
        var delegateType = typeof(Func<,>).MakeGenericType(groupByType, constructed);
    
        grouping = Expression.Lambda(
                       delegateType,
                       Expression.New(
                           constructed.GetConstructor(constructed.GetGenericArguments()),
                           keyMembers.ToArray()),
                       groupByParam);
    }
    
[/code]


CC BY-SA 3.0
2015-01-26T14:06:53.240
5348
2015-01-27T14:07:39.723
2015-01-27T14:07:39.723
5750
5750
5342
answer


質問の意図がいまいち理解できませんでした。何か問題があるわけではなく、既に実現できているわけですよね？
Expressionを使いたくないということでしょうか？  
とりあえず思い付いたコードを挙げますので、何か参考になればと。

GroupBy()は[keySelectorの他にcomparerも受け取るオーバーロード](https://msdn.microsoft.com/ja-
jp/library/bb534334\(v=vs.110\).aspx)が存在します。希望するcomparerを動的に作れば実現できそうに思います。

[code]

    class GroupByEntityEqualityComparer : IEqualityComparer<GroupByEntity> {
        public bool CompareKey1 { get; set; }
        public bool CompareKey2 { get; set; }
        public bool CompareKey3 { get; set; }
        public bool CompareKey4 { get; set; }
        public bool CompareKey5 { get; set; }
    
        public bool Equals(GroupByEntity x, GroupByEntity y) {
            if (CompareKey1 && !x.key1.Equals(y.key1)) return false;
            if (CompareKey2 && !x.key2.Equals(y.key2)) return false;
            if (CompareKey3 && !x.key3.Equals(y.key3)) return false;
            if (CompareKey4 && !x.key4.Equals(y.key4)) return false;
            if (CompareKey5 && !x.key5.Equals(y.key5)) return false;
            return true;
        }
    
        public int GetHashCode(GroupByEntity obj) {
            int hashCode = 0;
            if (CompareKey1) hashCode ^= obj.key1.GetHashCode();
            if (CompareKey2) hashCode ^= obj.key2.GetHashCode();
            if (CompareKey3) hashCode ^= obj.key3.GetHashCode();
            if (CompareKey4) hashCode ^= obj.key4.GetHashCode();
            if (CompareKey5) hashCode ^= obj.key5.GetHashCode();
            return hashCode;
        }
    }
    
[/code]

を利用して

[code]

    // 修正後
    var comparer = new GroupByEntityEqualityComparer();
    if (条件１) comparer.CompareKey1 = true;
    if (条件２) comparer.CompareKey2 = true;
    if (条件３) comparer.CompareKey3 = true;
    if (条件４) comparer.CompareKey4 = true;
    if (条件５) comparer.CompareKey5 = true;
    // ここまで
    var list = groupByList
        .GroupBy(m => m, comparer)
        .Select(m => new GroupByEntity() {
            key1 = selectUniqueValue(m.Select(val => val.key1)),
            key2 = selectUniqueValue(m.Select(val => val.key2)),
            key3 = selectUniqueValue(m.Select(val => val.key3)),
            key4 = selectUniqueValue(m.Select(val => val.key4)),
            key5 = selectUniqueValue(m.Select(val => val.key5)),
            value1 = m.Max(val => val.value1),
            value2 = m.Max(val => val.value2),
            value3 = m.Max(val => val.value3)
        })
        .ToList();
    
[/code]


CC BY-SA 3.0
2015-01-26T14:21:14.410
5349
2015-01-27T13:55:13.143
2015-01-27T13:55:13.143
4236
4236
5342
answer