【问题标题】:C#: Getting maximum and minimum values of arbitrary properties of all items in a listC#:获取列表中所有项目的任意属性的最大值和最小值
【发布时间】:2010-09-14 05:38:54
【问题描述】:

我有一个专门的列表,其中包含 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...]
}

有时我需要知道列表中所有事物的某个属性的最大值或最小值。由于“告诉不问”,我们让列表弄清楚:

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。谢谢,伙计们。

结论:有很多不同的方法比反射要好得多。取决于运行时和 C# 版本。看看 Jon Skeets 对差异的回答。所有答案都非常有帮助。

我会寻求 Sklivvz 的建议(匿名方法)。有几个来自其他人(Konrad Rudolph、Matt Hamilton 和 Coincoin)的代码 sn-ps 实现了 Sklivvz 的想法。很遗憾,我只能“接受”一个答案。

非常感谢。你们都可以感到“被接受”,尽管只有 Sklivvz 获得了学分;-)

【问题讨论】:

  • 我添加了一个有效的实现

标签: c# .net reflection .net-2.0


【解决方案1】:

(已编辑以反映 .NET 2.0 的答案,以及 VS2005 中的 LINQBridge...)

这里有三种情况——虽然OP只有.NET 2.0,但其他面临同样问题的人可能没有……

1) 使用 .NET 3.5 和 C# 3.0:使用 LINQ to Objects,如下所示:

decimal maxWeight = list.Max(thing => thing.Weight);
decimal minWeight = list.Min(thing => thing.Weight);

2) 使用 .NET 2.0 和 C# 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。)

LINQBridge 将与 VS2005 一起使用,但您不会获得扩展方法、lambda 表达式、查询表达式等。显然迁移到 C# 3 是一个更好的选择,但我更喜欢使用 LINQBridge 来自己实现相同的功能。

如果您需要同时获得最大值和最小值,所有这些建议都涉及遍历列表两次。如果您遇到从磁盘延迟加载或类似情况的情况,并且您想一次性计算多个聚合,您可能需要查看我在MiscUtil 中的"Push LINQ" 代码。 (这也适用于 .NET 2.0。)

【讨论】:

    【解决方案2】:

    如果您使用的是 .NET 3.5 和 LINQ:

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

    这将使 Min 和 Max 的计算变得相当简单。

    【讨论】:

      【解决方案3】:

      是的,您应该使用委托和匿名方法。

      示例见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

      【讨论】:

        【解决方案4】:

        如果使用 .NET 3.5,为什么不使用 lambda?

        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】:

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

          【讨论】:

            【解决方案6】:

            这是根据 Skilwz 的想法使用 C# 2.0 进行的尝试。

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

            【讨论】:

            • 此方法允许您获取其类型实现 IComparable 的任何属性的最大值。因此,您可以获得最大值,例如 DateTime 属性或字符串以及小数。
            • Matt,它可以使用一个返回项目之间“最佳”的委托来概括。如果“最佳”是您生成的最小值,如果它越大,那么您将获得最大值。
            • 好点。如果委托类型是“ThingComparer”,采用两个 IThings 并返回一个布尔值,那么此方法可以同时执行 Max 和 Min。不过,不知道这与提问者的“告诉不问”哲学有什么关系。
            【解决方案7】:

            结论:.Net 2.0(使用 Visual Studio 2005)没有更好的方法。

            您似乎误解了答案(尤其是 Jon 的)。您可以使用他回答中的选项 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; } );
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2017-09-20
                • 2020-09-22
                • 2020-08-16
                • 2021-09-17
                • 1970-01-01
                • 2010-11-23
                • 1970-01-01
                相关资源
                最近更新 更多