我正在寻找一种按功能对集合进行排序的有效方法,并同时根据该函数的评估结果对集合进行过滤。为了说明:
var materialsByExpiry = from m in materials
where m.ExpiryDate() >= today
orderby m.ExpiryDate()
select m;
由于materials
是一个大集合,并且ExpiryDate
是一个非平凡的计算,所以我想减少对ExpiryDate
的调用次数。显然,ExpiryDate
每种材料只需要调用一次,但是此代码将其调用 n + q 次,其中 n 为元素总数, q 是通过过滤器的数量。
我可以看到一种可能性是定义一种结构,该结构可以存储物料及其有效期并进行处理。但是,有没有一种更有效的方法(花时间去制定自己的排序/过滤器算法)呢?
答案 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);
}
}
这是比较输出:
答案 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;
虽然命名很尴尬。您不能通过这种方式为元组的属性赋予有意义的名称。