在C#3.5中,只需一两行即可从集合中返回最合适的物品

在C#3.5中,只需一两行即可从集合中返回最合适的物品,c#,linq,max,C#,Linq,Max,下面是一些我一生中基本上写过数千次的示例代码: // find bestest thingy Thing bestThing; float bestGoodness = FLOAT_MIN; foreach( Thing x in arrayOfThings ) { float goodness = somefunction( x.property, localvariable ); if( goodness > bestGoodness ) { bestGoodnes

下面是一些我一生中基本上写过数千次的示例代码:

// find bestest thingy
Thing bestThing;
float bestGoodness = FLOAT_MIN;
foreach( Thing x in arrayOfThings )
{
  float goodness = somefunction( x.property, localvariable );
  if( goodness > bestGoodness )
  {
    bestGoodness = goodness;
    bestThing = x;
  }
}
return bestThing;
在我看来,C#应该已经有了某种东西,可以在一行中实现这一点。比如:

return arrayOfThings.Max( delegate(x)
  { return somefunction( x.property, localvariable ); });
var sortedByGoodness = from x in arrayOfThings 
  orderby somefunction( x.property, localvariable ) ascending 
  select x;
return x.first;
但这并不返回返回拟合优度值的对象(或对象的索引,这很好)

所以可能是这样的:

return arrayOfThings.Max( delegate(x)
  { return somefunction( x.property, localvariable ); });
var sortedByGoodness = from x in arrayOfThings 
  orderby somefunction( x.property, localvariable ) ascending 
  select x;
return x.first;
但这样做需要整个阵列,可能太慢了


这是否存在?

这是您可以使用System.Linq执行的操作:

var value = arrayOfThings
    .OrderByDescending(x => somefunction(x.property, localvariable))
    .First();
如果数组可以为空,请使用
.FirstOrDefault()以避免异常

您确实不知道这是如何在内部实现的,因此无法保证这将对整个数组进行排序以获得第一个元素。例如,如果是LINQtoSQL,服务器将收到一个包含排序和条件的查询。它不会获取数组,然后对其排序,然后获取第一个元素

事实上,除非您不先调用,否则不会计算查询的第一部分。我的意思是这不是两步评估,而是一步评估

var sortedValues =arrayOfThings
  .OrderByDescending(x => somefunction(x.property, localvariable));
// values isn't still evaluated
var value = sortedvalues.First();
// the whole expression is evaluated at this point.

我认为在标准LINQ中,如果不对enuerable进行排序(在一般情况下这很慢),这是不可能的,但是您可以使用MoreLinq库中的
MaxBy()
方法来实现这一点。我总是在我的项目中包含这个库,因为它非常有用


(代码实际上看起来非常类似于您所拥有的,但是是泛化的。)

我将实现
IComparable
,只需使用
arrayOfThings.Max()

示例如下:

我认为这是最干净的方法,IComparable可能在其他地方也有用

更新

还有一个重载的
Max
方法,它采用投影函数,因此您可以提供不同的逻辑来获取身高、年龄等


我按照注释中列出的链接Porges,在中运行以下代码,并验证两个LINQ表达式都返回了正确答案


void Main()
{
    var things = new Thing [] {
        new Thing { Value = 100 },
        new Thing { Value = 22 },
        new Thing { Value = 10 },
        new Thing { Value = 303 },
        new Thing { Value = 223}
    };

    var query1 = (from t in things
                orderby GetGoodness(t) descending 
                select t).First();

    var query2 = things.Aggregate((curMax, x) => 
        (curMax == null || (GetGoodness(x) > GetGoodness(curMax)) ? x : curMax));   
}

int GetGoodness(Thing thing)
{
    return thing.Value * 2;
}

public class Thing 
{
    public int Value {get; set;}
}

来自LinqPad的结果


通过使用
Aggregate
,您实际上可以在不使用普通LINQ排序的情况下完成这项工作,但除了最简单的情况外,它最终都非常难看。最好使用某种
MaxBy
扩展方法。但是如果没有一种真正的方法来比较事情呢?例如,如果对象是人,则可以实现IComparable来超越他们的高度。但是,如果将来你想比较年龄会发生什么呢?同样,这与OP中的Max()有相同的问题;它返回的是值,而不是对象或对象的索引。@Porges感谢您指出链接。很高兴知道:)您不能在Linq2SQL中按任意函数排序-它必须是一个可以轻松转换为SQL ORDER BY子句的表达式。因此,我认为可以安全地假设我们将这个问题的范围限制在LINQ2对象上,因此枚举的排序可能是一个问题