首页 > 解决方案 > IEnumerable.Select 的动态 lambda

问题描述

我需要将一个大表分解为一系列 2 列的表,以便为配置器引擎动态创建表规则。这段代码演示了这个问题:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Spike
{
    class Program
    {
        static void Main(string[] args)
        {
            // The actual data I need to break down has ~20 properties of type string and decimal, over 18,000 rows
            var data = new List<MyData>()
            {
                new MyData("one", "two", 3m, "four"),
                new MyData("five", "six", 7m, "eight"),
                new MyData("nine", "ten", 11m, "twelve"),
                new MyData("thirteen", "fourteen", 15m, "sixteen"),
                new MyData("one", "five", 9m, "thirteen"),
                new MyData("two", "six", 10m, "fourteen"),
                new MyData("three", "seven", 11m, "fifteen"),
                new MyData("four", "eight", 12m, "sixteen")
            };

            // This shows the desired combinations of properties
            // The actual data will have ~230 combinations
            var properties = typeof(MyData).GetProperties(BindingFlags.Instance | BindingFlags.Public);
            for (var i = 0; i < properties.Length - 1; i++)
            {
                for (var j = i + 1; j < properties.Length; j++)
                {
                    Console.WriteLine($"{properties[i].Name} <=> {properties[j].Name}");
                }
            }
            /* output:
                P1 <=> P2
                P1 <=> P3
                P1 <=> P4
                P2 <=> P3
                P2 <=> P4
                P3 <=> P4
            */

            // This shows how I want one combination to appear
            // The challenge seems to be the creation of a dynamic lambda in the Select method.
            var items = data.Select(x => new { x.P2, x.P3 }).Distinct().ToList();
            Console.WriteLine();
            items.ForEach(x => Console.WriteLine($"{x.P2}, {x.P3}"));
            /* output:
                two, 3
                six, 7
                ten, 11
                fourteen, 15
                five, 9
                six, 10
                seven, 11
                eight, 12
            */

            Console.ReadKey();
        }
    }

    public class MyData
    {
        public string P1 { get; set; }
        public string P2 { get; set; }
        public decimal P3 { get; set; }
        public string P4 { get; set; }

        public MyData(string p1, string p2, decimal p3, string p4)
        {
            P1 = p1;
            P2 = p2;
            P3 = p3;
            P4 = p4;
        }
    }
}

我研究了 Linq、Reflection 和 Expression Trees,但似乎无法克服动态构建此表达式的障碍:

var items = data.Select(x => new { x.P2, x.P3 }).Distinct().ToList();

其中 x.P2 和 x.P3 是动态的。

这篇文章似乎朝着正确的方向前进,但我没有得到结果。

建议?提前致谢!

标签: c#linqreflectionexpression-trees

解决方案


我希望我能正确理解你的问题。这是枚举所需对的简单扩展:

var items = data.EnumeratePropPairs().Distinct().ToList();
items.ForEach(x => Console.WriteLine($"{x.Item1}, {x.Item2}"));

和实施

public static class EnumerableExtensions
{
    public static IEnumerable<Tuple<string, string>> EnumeratePropPairs<T>(this IEnumerable<T> items)
    {
        var properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
        var param = Expression.Parameter(typeof(T));
        var accessors = properties.ToDictionary(p => p, p =>
        {
            var body = (Expression)Expression.MakeMemberAccess(param, p);
            if (body.Type != typeof(string))
            {
                body = Expression.Call(body, "ToString", Type.EmptyTypes);
            }

            var lambda = Expression.Lambda<Func<T, string>>(body, param);
            return lambda.Compile();
        });

        var pairs = new List<Tuple<Func<T, string>, Func<T, string>>>();

        for (var i = 0; i < properties.Length - 1; i++)
        {
            var prop1 = properties[i];
            var prop1Accessor = accessors[prop1];

            for (var j = i + 1; j < properties.Length; j++)
            {
                var prop2 = properties[j];
                var prop2Accessor = accessors[prop2];

                pairs.Add(Tuple.Create(prop1Accessor, prop2Accessor));
            }
        }

        return items.SelectMany(item => pairs.Select(p => Tuple.Create(p.Item1(item), p.Item2(item))));
    }
}

推荐阅读