18

タイプのアイテムを保持する特殊なリストがあります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*()すべてのプロパティにペアは必要ありません。

解決策の 1 つは反射です。次のようなもの (鼻をほじって、コードの匂いが強い!):

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 の提案 (匿名の方法) に行きます。Sklivvz のアイデアを実装する他の人々 (Konrad Rudolph、Matt Hamilton、および Coincoin) からのコード スニペットがいくつかあります。残念ながら、私は1つの答えしか「受け入れる」ことができません。

どうもありがとうございました。Sklivvzだけがクレジットを取得しますが、誰もが「受け入れられた」と感じることができます;-)

4

8 に答える 8

33

(.NET 2.0 の回答と VS2005 の LINQBridge を反映するように編集されています...)

ここには 3 つの状況があります。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<IThing,decimal> にキャストします。)

LINQBridge は VS2005 で動作しますが、拡張メソッド、ラムダ式、クエリ式などは取得できません。明らかに C# 3 に移行する方が適切ですが、同じ機能を自分で実装するよりも LINQBridge を使用することをお勧めします。

最大値と最小値の両方を取得する必要がある場合、これらの提案はすべて、リストを 2 回調べる必要があります。ディスクから遅延してロードしている状況などで、一度に複数の集計を計算したい場合は、 MiscUtilの「Push LINQ」コードを参照してください。(これは .NET 2.0 でも機能します。)

于 2008-09-30T11:21:11.680 に答える
19

.NET 3.5 と LINQ を使用している場合:

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

これにより、最小値と最大値の計算がかなり簡単になります。

于 2008-09-30T11:18:47.053 に答える
10

はい、デリゲートと匿名メソッドを使用する必要があります。

例については、こちらを参照してください。

基本的に、 Listsの Find メソッドに似たものを実装する必要があります。

これがサンプル実装です

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

于 2008-09-30T11:21:23.413 に答える
8

.NET 3.5 を使用している場合、ラムダを使用しないのはなぜですか?

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

これは強く型付けされ、効率的です。まさにこれをすでに行っている拡張メソッドもあります。

于 2008-09-30T11:20:36.223 に答える
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);
于 2008-09-30T12:09:13.437 に答える
3

これは、C# 2.0 を使用した、Skillwz のアイデアの試みです。

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; });
于 2008-09-30T12:17:54.477 に答える
2

一般化された.Net2ソリューションはどうですか?

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; } );
}
于 2008-09-30T13:17:37.003 に答える
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; });
于 2008-09-30T12:07:49.540 に答える