C#:获取列表中所有项的任意属性的最大值和最小值
本文关键字:任意 属性 最大值 最小值 获取 列表 | 更新日期: 2023-09-27 17:48:52
我有一个专门的列表,其中包含类型为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);
有没有更好、气味更小的方法可以做到这一点?
谢谢,Eric
编辑:@Matt:.Net 2.0
结论:对于.Net 2.0(使用Visual Studio 2005),没有更好的方法了。也许我们应该很快转向.Net 3.5和Visual Studio 2008。谢谢,伙计们
结论:有不同的方法比反思要好得多。取决于运行时和C#版本。看看Jon Skeets对差异的回答。所有的答案都很有帮助。
我会选择Sklivz的建议(匿名方法)。还有一些来自其他人(Konrad Rudolph、Matt Hamilton和Coincoin)的代码片段实现了Sklivz的想法。不幸的是,我只能"接受"一个答案。
非常感谢。你们都能感觉到"被接受了",尽管只有斯克利夫兹获得了学分;-)
(经过编辑以反映.NET 2.0的答案,以及VS2005中的LINQBridge…)
这里有三种情况——尽管OP只有.NET 2.0,但其他面临同样问题的人可能不会。。。
1) 使用.NET 3.5和C#3.0:对以下对象使用LINQ:
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<IThing,decimal>。)
LINQBridge将与VS2005一起使用,但您没有扩展方法、lambda表达式、查询表达式等。显然,迁移到C#是一个更好的选择,但我更喜欢使用LINQBridger来实现相同的功能。
如果你需要同时获得最大值和最小值,所有这些建议都需要在列表中遍历两次。如果你遇到了从磁盘缓慢加载的情况,或者类似的情况,并且你想一次计算几个聚合,你可能想看看我在MiscUtil中的"Push LINQ"代码。(这也适用于.NET 2.0。)
如果您使用的是.NET 3.5和LINQ:
Decimal result = myThingList.Max(i => i.Weight);
这将使Min和Max的计算变得相当琐碎。
是的,您应该使用委托和匿名方法。
有关示例,请参见此处。
基本上,您需要实现类似于Lists的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());
}
}
结果:
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 3.5,为什么不使用lambdas?
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);
这是强类型的并且是有效的。还有一些扩展方法已经做到了这一点。
对于C#2.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;});
以下是如何使用C#3.0、.Net 3.5和Linq,而不使用上面的函数:
string[] array = new string[] {"s","sss","ddsddd","333","44432333"};
array.Max( e => e.Length);
下面是一个尝试,使用C#2.0来实现Skilwz的想法。
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.0(使用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; });
一个通用的.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; } );
}