I have a specialized list that holds items of type IThing
:
public class ThingList : IList
{...}
public interface IThing
{
Decimal
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);
(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.)
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; } );
}
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.
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.
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