C#:获取列表中所有项目的任意属性的最大值和最小值
我有一个专门的列表,其中包含IThing
类型的项目:
public class ThingList : IList<IThing> {...} 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<IThing> { 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. 谢谢你们。
结论:有很多方法比反射更好。取决于运行时和Cversion。看看Jon Skeets回答的差异。所有答案都非常有帮助。
我将寻求Sklivvz的建议(匿名方法)。其他人(Konrad Rudolph,Matt Hamilton和Coincoin)的一些代码片段实现了Sklivvz的想法。不幸的是,我只能"接受"一个答案。
非常感谢你。你们所有人都会感到"被接受",尽管只有Sklivvz获得了积分;-)
解决方案
如果我们使用的是.NET 3.5和LINQ:
Decimal result = myThingList.Max(i => i.Weight);
这将使Min和Max的计算相当琐碎。
如果使用.NET 3.5,为什么不使用lambda?
public Decimal GetMaximum(Func<IThing, Decimal> 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);
这是强类型且高效的。也有扩展方法已经完全可以做到这一点。
(经过编辑以反映.NET 2.0答案以及VS2005中的LINQBridge ...)
尽管OP仅具有.NET 2.0,但在三种情况下,遇到相同问题的其他人可能没有...
1)使用.NET 3.5和C3.0:对以下对象使用LINQ:
decimal maxWeight = list.Max(thing => thing.Weight); decimal minWeight = list.Min(thing => thing.Weight);
2)使用.NET 2.0和C3.0:使用LINQBridge和相同的代码
3)使用.NET 2.0和C2.0:使用LINQBridge和匿名方法:
decimal maxWeight = Enumerable.Max(list, delegate(IThing thing) { return thing.Weight; } ); decimal minWeight = Enumerable.Min(list, delegate(IThing thing) { return thing.Weight; } );
(如果抱怨转换不明确,我没有C2.0编译器来测试上述内容,请将委托转换为Func <IThing,decimal>。)
LINQBridge可以与VS2005一起使用,但是我们没有扩展方法,lambda表达式,查询表达式等。显然,迁移到C3是一个更好的选择,但是我更喜欢使用LINQBridge自己实现相同的功能。
所有这些建议都涉及到两次遍历列表(如果我们需要同时获取最大值和最小值)。如果我们遇到从磁盘延迟加载或者类似的情况,并且想要一次性计算多个聚合,则可能需要在MiscUtil中查看我的" Push LINQ"代码。 (这也适用于.NET 2.0。)
是的,我们应该使用委托和匿名方法。
有关示例,请参见此处。
基本上,我们需要实现类似于"列表"的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<Thing> { 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()); } }
结果:
日期上限:I:4,S:w,D:10/3/2008 12:44:07 AM
按字符的最小值:I:5,S:v,D:2008年9月29日上午12:44:07
Conclusion: There is no better way for .Net 2.0 (with Visual Studio 2005).
我们似乎误解了答案(尤其是乔恩的答案)。我们可以从他的答案中使用选项3. 如果我们不想使用LinqBridge,我们仍然可以使用委托并自己实现Max方法,类似于我发布的方法:
delegate Decimal PropertyValue(IThing thing); public class ThingList : IList<IThing> { 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; });
对于C2.0和.Net 2.0,我们可以对Max执行以下操作:
public delegate Decimal GetProperty<TElement>(TElement element); public static Decimal Max<TElement>(IEnumerable<TElement> enumeration, GetProperty<TElement> 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;});
在没有上述功能的情况下,使用C3.0,.Net 3.5和Linq的方法如下:
string[] array = new string[] {"s","sss","ddsddd","333","44432333"}; array.Max( e => e.Length);
这是根据Skilwz的想法使用C2.0进行的尝试。
public delegate T GetPropertyValueDelegate<T>(IThing t); public T GetMaximum<T>(GetPropertyValueDelegate<T> 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解决方案如何?
public delegate A AggregateAction<A, B>( A prevResult, B currentElement ); public static Tagg Aggregate<Tcoll, Tagg>( IEnumerable<Tcoll> source, Tagg seed, AggregateAction<Tagg, Tcoll> func ) { Tagg result = seed; foreach ( Tcoll element in source ) result = func( result, element ); return result; } //this makes max easy public static int Max( IEnumerable<int> source ) { return Aggregate<int,int>( source, 0, delegate( int prev, int curr ) { return curr > prev ? curr : prev; } ); } //but you could also do sum public static int Sum( IEnumerable<int> source ) { return Aggregate<int,int>( source, 0, delegate( int prev, int curr ) { return curr + prev; } ); }