2008-09-30 2 views
18

У меня есть специализированный список, который содержит элементы типа IThing:C#: Получение максимальных и минимальных значений произвольных свойств всех элементов в списке

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

Thats very nice. Но иногда мне нужен минимальный вес, иногда максимальная скорость и так далее. Я не хочу пару 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. Спасибо, парни.

Заключение: Существуют различные способы, которые намного лучше, чем отражение. В зависимости от времени исполнения и версии C#. Взгляните на Jon Skeets, ответьте на различия. Все ответы очень полезны.

Я поеду для предложения Sklivvz (анонимные методы). Существует несколько фрагментов кода от других людей (Конрад Рудольф, Мэтт Гамильтон и Коинкойн), которые реализуют идею Скливвца. К сожалению, я могу только «принять» один ответ.

спасибо. Вы все можете чувствовать себя «принято», Altough только Sklivvz получает кредиты ;-)

+0

Я добавил рабочую реализацию – Sklivvz 2008-09-30 23:56:40

ответ

10

Да, вы должны использовать делегатский и анонимный методы.

Для примера см. here.

В основном вам необходимо реализовать что-то похожее на Find method of Lists.

Вот пример реализации

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

+0

Это просто потрясающе. – 2011-07-26 18:47:26

19

Если вы вы используете .NET 3.5 и LINQ:

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

Что бы сделать расчет Min и Max довольно тривиальной ,

8

Если вы используете .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); 

Это сильно типизированных и эффективно. Существуют также методы расширения, которые уже выполняют именно это.

31

(под редакцией отражать .NET 2.0 ответ, и LINQBridge в VS2005 ...)

Есть три ситуации здесь - хотя OP имеет только .NET 2.0, другие люди, сталкивающиеся с той же проблемой, не может .. .

1) с помощью .NET 3.5 и C# 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 и 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 < IThing, decimal>.)

LINQBridge будет работать с VS2005, но вы не получите методы расширения, лямбда-выражения, выражения запросов и т. д. . Очевидно, что переход на C# 3 является более приятным вариантом, но я бы предпочел использовать LINQBridge для реализации того же функциональность.

Все эти рекомендации включают в себя просмотр списка дважды, если вам нужно получить максимальный и минимальный. Если у вас есть ситуация, когда вы загружаетесь с диска лениво или что-то в этом роде, и вы хотите рассчитать несколько агрегатов за один раз, вы можете посмотреть мой код "Push LINQ" в MiscUtil. (Это также работает с .NET 2.0.)

2

Заключение: Там нет лучшего способа для .Net 2.0 (с Visual Studio 2005).

Вы, кажется, не поняли ответы (особенно Джон). Вы можете использовать опцию 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; }); 
+0

Джон отредактировал свой ответ, когда я делал вывод. – EricSchaefer 2008-09-30 12:15:04

3

Для 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); 
3

Вот попытка, используя C# 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; }); 
+0

Этот метод позволяет получить максимум любого свойства, тип которого реализует IComparable. Таким образом, вы можете получить максимум, скажем, свойство DateTime или строку, а также Decimals. – 2008-09-30 12:21:49

2

Как насчет обобщенного решения .Net 2?

Смежные вопросы