C#:获取列表中所有项的任意属性的最大值和最小值

我有一个专门的列表,其中包含IThing类型的IThing

 public class ThingList : IList {...} public interface IThing { Decimal Weight { get; set; } Decimal Velocity { get; set; } Decimal Distance { get; set; } Decimal Age { get; set; } Decimal AnotherValue { get; set; } [...even more properties and methods...] } 

有时我需要知道列表中所有内容的某个属性的最大值或最小值。 因为“告诉不要问”我们让列表弄明白:

 public class ThingList : IList { public Decimal GetMaximumWeight() { Decimal result = 0; foreach (IThing thing in this) { result = Math.Max(result, thing.Weight); } return result; } } 

这是非常好的。 但有时我需要最小重量,有时最大速度等等。 我不希望每个属性都有GetMaximum*()/GetMinimum*()对。

一种解决方案是反思。 像(抓住你的鼻子,强烈的代码味道!):

 Decimal GetMaximum(String propertyName); Decimal GetMinimum(String propertyName); 

有没有更好,更少臭的方法来实现这一目标?

谢谢,埃里克

编辑:@Matt:.Net 2.0

结论:.Net 2.0(使用Visual Studio 2005)没有更好的方法。 也许我们应该很快转移到.Net 3.5和Visual Studio 2008。 多谢你们。

结论:有不同的方法远比反思好。 取决于运行时和C#版本。 看看Jon Skeets对差异的回答。 所有答案都非常有用。

我会选择Sklivvz的建议(匿名方法)。 其他人(Konrad Rudolph,Matt Hamilton和Coincoin)有几个代码片段,它们实现了Sklivvz的想法。 不幸的是,我只能“接受”一个答案。

非常感谢你。 你们都可以感受到“接受”,尽管只有Sklivvz获得学分;-)

是的,您应该使用委托和匿名方法。

举个例子,请看这里 。

基本上你需要实现类似于List的Find方法的东西。

这是一个示例实现

 public class Thing { public int theInt; public char theChar; public DateTime theDateTime; public Thing(int theInt, char theChar, DateTime theDateTime) { this.theInt = theInt; this.theChar = theChar; this.theDateTime = theDateTime; } public string Dump() { return string.Format("I: {0}, S: {1}, D: {2}", theInt, theChar, theDateTime); } } public class ThingCollection: List { public delegate Thing AggregateFunction(Thing Best, Thing Candidate); public Thing Aggregate(Thing Seed, AggregateFunction Func) { Thing res = Seed; foreach (Thing t in this) { res = Func(res, t); } return res; } } class MainClass { public static void Main(string[] args) { Thing a = new Thing(1,'z',DateTime.Now); Thing b = new Thing(2,'y',DateTime.Now.AddDays(1)); Thing c = new Thing(3,'x',DateTime.Now.AddDays(-1)); Thing d = new Thing(4,'w',DateTime.Now.AddDays(2)); Thing e = new Thing(5,'v',DateTime.Now.AddDays(-2)); ThingCollection tc = new ThingCollection(); tc.AddRange(new Thing[]{a,b,c,d,e}); Thing result; //Max by date result = tc.Aggregate(tc[0], delegate (Thing Best, Thing Candidate) { return (Candidate.theDateTime.CompareTo( Best.theDateTime) > 0) ? Candidate : Best; } ); Console.WriteLine("Max by date: {0}", result.Dump()); //Min by char result = tc.Aggregate(tc[0], delegate (Thing Best, Thing Candidate) { return (Candidate.theChar < Best.theChar) ? Candidate : Best; } ); Console.WriteLine("Min by char: {0}", result.Dump()); } } 

结果:

Max by date: I: 4, S: w, D: 10/3/2008 12:44:07 AM
Min by char: I: 5, S: v, D: 9/29/2008 12:44:07 AM

(编辑反映.NET 2.0的答案,以及VS2005中的LINQBridge …)

这里有三种情况 – 虽然OP只有.NET 2.0,但其他人面临同样的问题可能不会……

1)使用.NET 3.5和C#3.0:像这样使用LINQ to Objects:

 decimal maxWeight = list.Max(thing => thing.Weight); decimal minWeight = list.Min(thing => thing.Weight); 

2)使用.NET 2.0和C#3.0:使用LINQBridge和相同的代码

3)使用.NET 2.0和C#2.0:使用LINQBridge和匿名方法:

 decimal maxWeight = Enumerable.Max(list, delegate(IThing thing) { return thing.Weight; } ); decimal minWeight = Enumerable.Min(list, delegate(IThing thing) { return thing.Weight; } ); 

(我没有C#2.0编译器来测试上面的内容 – 如果它抱怨模糊转换,请将委托转换为Func 。)

LINQBridge将与VS2005一起使用,但是你没有获得扩展方法,lambda表达式,查询表达式等。显然,迁移到C#3是一个更好的选择,但我更喜欢使用LINQBridge自己实现相同的function。

如果您需要同时获得最大值和最小值,所有这些建议都涉及将列表移动两次。 如果你有一种懒惰地从磁盘上加载的情况,并且想要一次性计算几个聚合,你可能想在MiscUtil中查看我的“推送LINQ”代码。 (也适用于.NET 2.0。)

如果您使用的是.NET 3.5和LINQ:

 Decimal result = myThingList.Max(i => i.Weight); 

这将使得Min和Max的计算相当微不足道。

如果使用.NET 3.5,为什么不使用lambdas?

 public Decimal GetMaximum(Func prop) { Decimal result = Decimal.MinValue; foreach (IThing thing in this) result = Math.Max(result, prop(thing)); return result; } 

用法:

 Decimal result = list.GetMaximum(x => x.Weight); 

这是强类型和有效的。 还有一些扩展方法已经完成了这一点。

对于C#2.0和.Net 2.0,您可以对Max执行以下操作:

 public delegate Decimal GetProperty(TElement element); public static Decimal Max(IEnumerable enumeration, GetProperty getProperty) { Decimal max = Decimal.MinValue; foreach (TElement element in enumeration) { Decimal propertyValue = getProperty(element); max = Math.Max(max, propertyValue); } return max; } 

以下是您将如何使用它:

 string[] array = new string[] {"s","sss","ddsddd","333","44432333"}; Max(array, delegate(string e) { return e.Length;}); 

以下是如何使用C#3.0,.Net 3.5和Linq,没有上述function:

 string[] array = new string[] {"s","sss","ddsddd","333","44432333"}; array.Max( e => e.Length); 

这是在Skilwz的想法中尝试使用C#2.0。

 public delegate T GetPropertyValueDelegate(IThing t); public T GetMaximum(GetPropertyValueDelegate getter) where T : IComparable { if (this.Count == 0) return default(T); T max = getter(this[0]); for (int i = 1; i < this.Count; i++) { T ti = getter(this[i]); if (max.CompareTo(ti) < 0) max = ti; } return max; } 

你会像这样使用它:

 ThingList list; Decimal maxWeight = list.GetMaximum(delegate(IThing t) { return t.Weight; }); 

结论:.Net 2.0(使用Visual Studio 2005)没有更好的方法。

你似乎误解了答案(特别是Jon的)。 您可以从他的回答中使用选项3。 如果您不想使用LinqBridge,您仍然可以使用委托并自己实现Max方法,类似于我发布的方法:

 delegate Decimal PropertyValue(IThing thing); public class ThingList : IList { public Decimal Max(PropertyValue prop) { Decimal result = Decimal.MinValue; foreach (IThing thing in this) { result = Math.Max(result, prop(thing)); } return result; } } 

用法:

 ThingList lst; lst.Max(delegate(IThing thing) { return thing.Age; }); 

广义的.Net 2解决方案怎么样?

 public delegate A AggregateAction( A prevResult, B currentElement ); public static Tagg Aggregate( IEnumerable source, Tagg seed, AggregateAction func ) { Tagg result = seed; foreach ( Tcoll element in source ) result = func( result, element ); return result; } //this makes max easy public static int Max( IEnumerable source ) { return Aggregate( source, 0, delegate( int prev, int curr ) { return curr > prev ? curr : prev; } ); } //but you could also do sum public static int Sum( IEnumerable source ) { return Aggregate( source, 0, delegate( int prev, int curr ) { return curr + prev; } ); }