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

发布于 2024-07-07 12:35:01 字数 1413 浏览 5 评论 0原文

我有一个专门的列表,其中包含 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 对差异的回答。 所有答案都非常有帮助。

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

非常感谢。 你们都会感到“被接受”,尽管只有 Sklivvz 获得了荣誉;-)

I have a specialized list that holds items of type 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.

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

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.

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.

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.

Thank you very much. You can all feel "accepted", altough only Sklivvz gets the credits ;-)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

爱本泡沫多脆弱 2024-07-14 12:35:01

(编辑以反映.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< /a> 和相同的代码

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 自己实现相同的功能。

如果您需要同时获得最大值和最小值,所有这些建议都涉及遍历列表两次。 如果您遇到从磁盘延迟加载或类似情况的情况,并且您想一次性计算多个聚合,您可能需要查看我的 "推送 LINQ" 代码在 MiscUtil 中。 (这也适用于 .NET 2.0。)

(Edited to reflect .NET 2.0 answer, and LINQBridge in VS2005...)

There are three situations here - although the OP only has .NET 2.0, other people facing the same problem may not...

1) Using .NET 3.5 and C# 3.0: use LINQ to Objects like this:

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

2) Using .NET 2.0 and C# 3.0: use LINQBridge and the same code

3) Using .NET 2.0 and C# 2.0: use LINQBridge and anonymous methods:

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>.)

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.

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.)

软糖 2024-07-14 12:35:01

如果您使用 .NET 3.5 和 LINQ:

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

这将使 Min 和 Max 的计算变得相当简单。

If you were you using .NET 3.5 and LINQ:

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

That would make the calculation of Min and Max fairly trivial.

抹茶夏天i‖ 2024-07-14 12:35:01

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

有关示例,请参阅此处

基本上,您需要实现类似于 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:9/29/2008 12:44:07 AM

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

如果没有你 2024-07-14 12:35:01

如果使用 .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);

这是强类型且高效的。 还有一些扩展方法已经可以做到这一点。

If using .NET 3.5, why not use 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.

够运 2024-07-14 12:35:01

对于 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);

For C# 2.0 and .Net 2.0 you can do the following for 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:

string[] array = new string[] {"s","sss","ddsddd","333","44432333"};
array.Max( e => e.Length);
木有鱼丸 2024-07-14 12:35:01

这是根据 Skilwz 的想法使用 C# 2.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; });

Here's an attempt, using C# 2.0, at Skilwz's idea.

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; });
谁人与我共长歌 2024-07-14 12:35:01

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

您似乎误解了答案(尤其是 Jon 的答案)。 您可以使用他的答案中的选项 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; });

Conclusion: There is no better way for .Net 2.0 (with 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:

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; });
猛虎独行 2024-07-14 12:35:01

通用的 .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; } );
}

How about a generalised .Net 2 solution?

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; } );
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文