LINQ-按相同功能过滤和排序的最有效方法

时间:2019-05-21 10:43:19

标签: c# .net linq sorting

我正在寻找一种按功能对集合进行排序的有效方法,并同时根据该函数的评估结果对集合进行过滤。为了说明:

var materialsByExpiry = from m in materials
                        where m.ExpiryDate() >= today
                        orderby m.ExpiryDate()
                        select m;

由于materials是一个大集合,并且ExpiryDate是一个非平凡的计算,所以我想减少对ExpiryDate的调用次数。显然,ExpiryDate每种材料只需要调用一次,但是此代码将其调用 n + q 次,其中 n 为元素总数, q 是通过过滤器的数量。

我可以看到一种可能性是定义一种结构,该结构可以存储物料及其有效期并进行处理。但是,有没有一种更有效的方法(花时间去制定自己的排序/过滤器算法)呢?

2 个答案:

答案 0 :(得分:5)

您可以使用let关键字来临时保存ExpiryDate()返回的值

var materialsByExpiry = from m in materials
                        let date = m.ExpiryDate() 
                        where date >= today
                        orderby date
                        select m;

这会将呼叫数量减少到Where子句所需的数量。

以下是LINQPad的示例:

void Main()
{   
    DateTime today = DateTime.Now;  
    List<Material> materials = new List<UserQuery.Material>();

    materials.Add(new Material { Hours = 6 });
    materials.Add(new Material { Hours = 2 });
    materials.Add(new Material { Hours = -6 });
    materials.Add(new Material { Hours = -6 });
    materials.Add(new Material { Hours = 4 });

    // normal version
    var materialsByExpiry = (from m in materials
                            where m.ExpiryDate() >= today
                            orderby m.ExpiryDate()
                            select m).ToList();

    Material.count.Dump("COUNT normal");
    Material.count = 0;

    // LET version
    var materialsByExpiry_Let = (from m in materials
                            let date = m.ExpiryDate() 
                            where date >= today
                            orderby date
                            select m).ToList();

    Material.count.Dump("COUNT using LET");
    materialsByExpiry.Dump();
    materialsByExpiry_Let.Dump();
}

public class Material
{
    public static int count = 0;
    public int Hours { get; set; }
    public DateTime ExpiryDate()
    {
        count++;
        return DateTime.Now.AddHours(Hours);
    }
}

这是比较输出:

enter image description here

答案 1 :(得分:0)

您可以创建一个通用扩展方法,该方法调用计算密集型方法,并将输出与对象本身一起存储在ValueTuple中。

public static IEnumerable<(TSource Item, TProperty1 Property1)>
    Precompute<TSource, TProperty1>(this IEnumerable<TSource> source,
    Func<TSource, TProperty1> property1Selector)
{
    return source.Select(item => (item, property1Selector(item)));
}

然后您可以像这样使用它:

var materialsByExpiry = from m in materials.Precompute(m => m.ExpiryDate())
                        where m.Property1 >= today
                        orderby m.Property1
                        select m.Item;

虽然命名很尴尬。您不能通过这种方式为元组的属性赋予有意义的名称。