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条回答
你好瞎i
2楼-- · 2019-03-11 01:50

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.

查看更多
我欲成王,谁敢阻挡
3楼-- · 2019-03-11 01:50

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.

查看更多
聊天终结者
4楼-- · 2019-03-11 01:52

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; });
查看更多
Viruses.
5楼-- · 2019-03-11 01:57

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; });
查看更多
三岁会撩人
6楼-- · 2019-03-11 02:02

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","ddsffffd","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","ddsffffd","333","44432333"};
array.Max( e => e.Length);
查看更多
SAY GOODBYE
7楼-- · 2019-03-11 02:04

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

查看更多
登录 后发表回答