C#: Getting maximum and minimum values of arbitrar

2019-03-11 01:15发布

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

8条回答
劳资没心,怎么记你
2楼-- · 2019-03-11 02:04

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; } );
}
查看更多
Melony?
3楼-- · 2019-03-11 02:10

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

查看更多
登录 后发表回答