C#:获取列表中所有项目的任意属性的最大值和最小值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/152613/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C#: Getting maximum and minimum values of arbitrary properties of all items in a list
提问by EricSchaefer
I have a specialized list that holds items of type IThing
:
我有一个专门的列表,其中包含以下类型的项目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...]
}
Sometimes I need to know the maximum or minimum of a certain property of all the things in the list. Because of "Tell don't ask" we let the List figure it out:
有时我需要知道列表中所有事物的某个属性的最大值或最小值。由于“告诉不要问”,我们让列表弄清楚:
public class ThingList : IList<IThing>
{
public Decimal GetMaximumWeight()
{
Decimal result = 0;
foreach (IThing thing in this) {
result = Math.Max(result, thing.Weight);
}
return result;
}
}
Thats very nice. But sometimes I need the minimum weight, sometimes the maximum velocity and so on. I don't want a GetMaximum*()/GetMinimum*()
pair for every single property.
这是非常好的。但有时我需要最小重量,有时需要最大速度等等。我不希望GetMaximum*()/GetMinimum*()
每一个属性都有一对。
One solution would be reflection. Something like (hold your nose, strong code smell!):
一种解决方案是反射。像(捏住鼻子,强烈的代码气味!):
Decimal GetMaximum(String propertyName);
Decimal GetMinimum(String propertyName);
Are there any better, less smelly ways to accomplish this?
有没有更好、更不臭的方法来实现这一点?
Thanks, Eric
谢谢,埃里克
Edit: @Matt: .Net 2.0
编辑:@Matt:.Net 2.0
Conclusion: There is no better way for .Net 2.0 (with Visual Studio 2005). Maybe we should move to .Net 3.5 and Visual Studio 2008 sometime soon. Thanks, guys.
结论:.Net 2.0(使用 Visual Studio 2005)没有更好的方法。也许我们应该很快转向 .Net 3.5 和 Visual Studio 2008。谢谢你们。
Conclusion: There are diffent ways that are far better than reflection. Depending on runtime and C# version. Have a look at Jon Skeets answer for the differences. All answers are are very helpful.
结论:有很多方法比反射要好得多。取决于运行时和 C# 版本。看看 Jon Skeets 对差异的回答。所有的答案都非常有帮助。
I will go for Sklivvz suggestion (anonymous methods). There are several code snippets from other people (Konrad Rudolph, Matt Hamilton and Coincoin) which implement Sklivvz idea. I can only "accept" one answer, unfortunately.
我会去寻求 Sklivvz 建议(匿名方法)。有一些来自其他人(Konrad Rudolph、Matt Hamilton 和 Coincoin)的代码片段实现了 Sklivvz 的想法。不幸的是,我只能“接受”一个答案。
Thank you very much. You can all feel "accepted", altough only Sklivvz gets the credits ;-)
非常感谢。你们都可以感到“被接受”,尽管只有 Sklivvz 获得了学分;-)
采纳答案by Sklivvz
Yes, you should use a delegate and anonymous methods.
是的,您应该使用委托和匿名方法。
For an example see here.
有关示例,请参见此处。
Basically you need to implement something similar to the Find method of Lists.
Here is a sample implementation
这是一个示例实现
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());
}
}
The results:
结果:
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
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
回答by Matt Hamilton
If you were you using .NET 3.5 and LINQ:
如果您使用 .NET 3.5 和 LINQ:
Decimal result = myThingList.Max(i => i.Weight);
That would make the calculation of Min and Max fairly trivial.
这将使 Min 和 Max 的计算变得相当简单。
回答by Konrad Rudolph
If using .NET 3.5, why not use lambdas?
如果使用 .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;
}
Usage:
用法:
Decimal result = list.GetMaximum(x => x.Weight);
This is strongly typed and efficient. There are also extension methods that already do exactly this.
这是强类型且高效的。还有一些扩展方法已经做到了这一点。
回答by Jon Skeet
(Edited to reflect .NET 2.0 answer, and LINQBridge in VS2005...)
(编辑以反映 .NET 2.0 答案,以及 VS2005 中的 LINQBridge...)
There are three situations here - although the OP only has .NET 2.0, other people facing the same problem may not...
这里有三种情况——虽然 OP 只有 .NET 2.0,但其他面临同样问题的人可能不会......
1) Using .NET 3.5 and C# 3.0: use LINQ to Objects like this:
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) Using .NET 2.0 and C# 3.0: use LINQBridgeand the same code
2) 使用 .NET 2.0 和 C# 3.0:使用LINQBridge和相同的代码
3) Using .NET 2.0 and C# 2.0: use LINQBridgeand anonymous methods:
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; }
);
(I don't have a C# 2.0 compiler to hand to test the above - if it complains about an ambiguous conversion, cast the delegate to Func<IThing,decimal>.)
(我没有 C# 2.0 编译器来测试上述内容 - 如果它抱怨转换不明确,请将委托强制转换为 Func<IThing,decimal>。)
LINQBridge will work with VS2005, but you don't get extension methods, lambda expressions, query expressions etc. Clearly migrating to C# 3 is a nicer option, but I'd prefer using LINQBridge to implementing the same functionality myself.
LINQBridge 将与 VS2005 一起使用,但您不会获得扩展方法、lambda 表达式、查询表达式等。显然迁移到 C# 3 是一个更好的选择,但我更喜欢使用 LINQBridge 来实现相同的功能。
All of these suggestions involve walking the list twice if you need to get both the max and min. If you've got a situation where you're loading from disk lazily or something like that, and you want to calculate several aggregates in one go, you might want to look at my "Push LINQ"code in MiscUtil. (That works with .NET 2.0 as well.)
如果您需要获得最大值和最小值,所有这些建议都涉及两次遍历列表。如果您遇到了从磁盘延迟加载或类似情况的情况,并且您想一次性计算多个聚合,您可能需要查看我在MiscUtil 中的“Push LINQ”代码。(这也适用于 .NET 2.0。)
回答by Konrad Rudolph
Conclusion: There is no better way for .Net 2.0 (with Visual Studio 2005).
结论:.Net 2.0(使用 Visual Studio 2005)没有更好的方法。
You seem to have misunderstood the answers (especially Jon's). You can use option 3 from his answer.If you don't want to use LinqBridge you can still use a delegate and implement the Max
method yourself, similar to the method I've posted:
你似乎误解了答案(尤其是乔恩的)。您可以使用他的回答中的选项 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;
}
}
Usage:
用法:
ThingList lst;
lst.Max(delegate(IThing thing) { return thing.Age; });
回答by Coincoin
For C# 2.0 and .Net 2.0 you can do the following for Max:
对于 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;
}
And here is how you would use it:
这是您将如何使用它:
string[] array = new string[] {"s","sss","ddsddd","333","44432333"};
Max(array, delegate(string e) { return e.Length;});
Here is how you would do it with C# 3.0, .Net 3.5 and Linq, without the function above:
以下是使用 C# 3.0、.Net 3.5 和 Linq 执行此操作的方法,无需上述函数:
string[] array = new string[] {"s","sss","ddsddd","333","44432333"};
array.Max( e => e.Length);
回答by Matt Hamilton
Here's an attempt, using C# 2.0, at Skilwz's idea.
这是使用 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;
}
You'd use it like this:
你会像这样使用它:
ThingList list;
Decimal maxWeight = list.GetMaximum(delegate(IThing t) { return t.Weight; });
回答by Keith
How about a generalised .Net 2 solution?
通用的 .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; } );
}