С#: получение максимальных и минимальных значений произвольных свойств всех элементов в списке
У меня есть специализированный список, содержащий элементы типа 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...]
}
Иногда мне нужно знать максимум или минимум определенного свойства всех вещей в списке. Из-за того, что "Tell не спрашивайте", мы разрешаем списку:
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);
Есть ли лучшие, менее вонючие способы сделать это?
Спасибо,
Эрик
Изменить: @Matt:.Net 2.0
Заключение: нет лучшего способа для .Net 2.0 (с Visual Studio 2005). Возможно, мы скоро перейдем к .Net 3.5 и Visual Studio 2008. Спасибо, ребята.
Заключение: Существуют разные способы, которые намного лучше, чем отражение. В зависимости от времени исполнения и версии С#. Взгляните на Jon Skeets, ответьте на различия. Все ответы очень полезны.
Я поеду для предложения Sklivvz (анонимные методы). Существует несколько фрагментов кода от других людей (Конрад Рудольф, Мэтт Гамильтон и Коинкойн), которые реализуют идею Скливвца. К сожалению, я могу только "принять" один ответ.
Большое спасибо. Вы можете чувствовать себя "принятым", хотя только Sklivvz получает кредиты; -)
Ответы
Ответ 1
Да, вы должны использовать делегатские и анонимные методы.
Для примера см. здесь.
В основном вам нужно реализовать нечто похожее на Найти метод списков.
Вот пример реализации
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());
}
}
Результаты:
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
Ответ 2
(Отредактировано с учетом ответа .NET 2.0 и LINQBridge в VS2005...)
Здесь есть три ситуации: хотя OP имеет только .NET 2.0, другие люди, столкнувшиеся с одной и той же проблемой, могут не...
1) Использование .NET 3.5 и С# 3.0: используйте LINQ для таких объектов:
decimal maxWeight = list.Max(thing => thing.Weight);
decimal minWeight = list.Min(thing => thing.Weight);
2) Использование .NET 2.0 и С# 3.0: используйте LINQBridge и тот же код
3) Использование .NET 2.0 и С# 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; }
);
(У меня нет компилятора С# 2.0, чтобы проверить это: если он жалуется на неоднозначное преобразование, передайте делегат в Func < IThing, decimal > .)
LINQBridge будет работать с VS2005, но вы не получите методы расширения, лямбда-выражения, выражения запросов и т.д. Очевидно, что переход на С# 3 является более приятным вариантом, но я бы предпочел использовать LINQBridge для реализации той же самой функции.
Во всех этих предложениях нужно дважды перевести список, если вам нужно получить максимальный и минимальный. Если у вас есть ситуация, когда вы загружаетесь с диска лениво или что-то в этом роде, и вы хотите рассчитать несколько агрегатов за один раз, вы можете посмотреть на мой "Push LINQ" в MiscUtil. (Это также работает с .NET 2.0.)
Ответ 3
Если вы использовали .NET 3.5 и LINQ:
Decimal result = myThingList.Max(i => i.Weight);
Это сделало бы вычисление Min и Max довольно тривиальным.
Ответ 4
Если вы используете .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;
}
Использование:
Decimal result = list.GetMaximum(x => x.Weight);
Это строго типизировано и эффективно. Существуют также методы расширения, которые уже выполняют именно это.
Ответ 5
Для С# 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;});
Вот как вы это сделаете с С# 3.0,.Net 3.5 и Linq, без функции выше:
string[] array = new string[] {"s","sss","ddsddd","333","44432333"};
array.Max( e => e.Length);
Ответ 6
Здесь попытка, использующая С# 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;
}
Вы бы использовали его следующим образом:
ThingList list;
Decimal maxWeight = list.GetMaximum(delegate(IThing t) { return t.Weight; });
Ответ 7
Заключение: нет лучшего способа для .Net 2.0 (с Visual Studio 2005).
Вы, кажется, неправильно поняли ответы (особенно Jon's). Вы можете использовать вариант 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; });
Ответ 8
Как насчет обобщенного решения .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; } );
}