1

コードの特定の部分を最適化しようとしていますが、パフォーマンス ループがタイトになっています。主に、将来適用できる新しいことを学ぼうとしています。私の実装は非常に長いので、私が達成しようとしていることの一般的な例を挙げます。

私の質問はこれに関連しています: C# 'is' operator performance、特に選択された答えに関連しています。

クラス A があるとします。A から派生したクラス B もあります。タイプ A (A と B のタイプの混合を含む) のリストがあります。これらのアイテムを処理する方法で、オブジェクトの実際のタイプに基づいて特定の動作を実現したいと考えています (これが正しい言い方であるかどうかはわかりません。何か間違ったことを言っている場合は修正してください)。

void Process(A item)
{
   if (item is A)
   {
      DoBehavior((A)item); //I know the cast is redundant here, I'm just leaving 
                           //it here for my explanation.
   }
   else if (item is B)
   {
      DoBehavior((B)item);
   }
}

void DoBehaviour(A item)
{
   //perform necessary behaviour for type A
}

void DoBehaviour(B item)
{
   //perform necessary behaviour for type B
}

これが私が現在行っている方法です。A と B を含むタイプ A のリストを繰り返し処理していることに注意してください。また、状況を明確にするのに十分なコードを提供していないと思われる場合は、喜んで拡張します。

上に投稿した質問: C# 'is' operator performanceで、構造を変更して "as" 演算子を使用し、明示的なキャストを完全に取り除くことができることを学びました。

B bItem = item as B;

if (bItem  != null)
{
    DoBehavior(bItem);
}

これはすべて問題ありませんが、実際には、A と B だけでなく、C、D などもあり、すべて基本クラス A から派生しています。これにより、これらの if ステートメントの多くが発生します。最高のパフォーマンスを得るには、ネストする必要があります。

B bItem = item as B;

if (bItem  != null)
{
    DoBehavior(bItem);
}
else
{
   C cItem = item as C;
   if (cItem != null)
   {
      DoBehavior(cItem);
   }
   else
   {
      //and so on.
   }
}

今、これは醜いです。私はきちんとしたエレガントなコードを書くのが好きですが、それを行うのは非常に苦手です (そのため、見栄えを良くするために時間を無駄にすることがよくあります)。

この質問が広すぎないことを願っていますが、まず、関連する動作が実行されるように型を取得する際に、より最適でクリーンなソリューションがあるかどうかを知りたいと思います。そうでない場合、これらの「as」演算子をこのようにネストするよりもクリーンな方法はありますか?

別の方法として、動作を基本クラス A に移動し、派生クラスごとにオーバーライドすることが考えられます。ただし、より高い思考の意味では、私のこの特定のケースでの動作は、クラス A (またはその子) の動作ではなく、むしろ、それに作用/動作する外部クラスです (タイプごとに異なる動作をします)。 )。他に良い方法がなければ、今説明したように実装を強く検討しますが、これについて専門家の意見を求めたいと思います。

これを短くしようとしましたが、詳細を省略しすぎた可能性があります。その場合はお知らせください。

4

4 に答える 4

2

具体的なクラスを参照する代わりに、インターフェイスにプログラミングすることで、「if..elseif..elseif..」パスを回避することを強くお勧めします。

これを実現するには、最初にProcess()メソッドがそのパラメーターのタイプを認識しないようにします。おそらく、パラメータはのようなインターフェースになるでしょうIDoSomething

次に、直接Process()呼び出さないように実装DoSomething()します。DoSomething()メソッドの特定の実装に移動されるコードの小さなチャンクに分割する必要がありIDoSomethingます。このProcess()メソッドは、これらのメソッドを盲目的に呼び出します。つまり、IDoSomething一部のデータにコントラクトを適用します。

これは複雑になるほど面倒かもしれませんDoSomething()が、関心の分離がはるかに良くなり、互換性のあるタイプでProcess()あればIDoSomething、もう1つも書かなくても「オープン」になりelseます。

于 2012-05-28T22:54:12.243 に答える
2

私が見つけた状況に対する最善の解決策は、Double-Dispatch/Visitorパターンを使用することです。基本クラスAが抽象であり、具体的なクラスBとCがAから継承する状況について説明します。また、基本クラスAでDoBehaviorメソッドを抽象化することにより、必要な場所に実装を強制します。したがって、これを拡張してさらにタイプを追加する場合は、DoBehaviorメソッドを追加することを忘れないでください(忘れる可能性は低いようですが、この動作は、追加する新しいタイプの残りの部分にとって重要ではなく、見落とされる可能性があります-特にこれらの行動パターンの多くがある場合)

interface IVisitor
{
   void DoBehavior(B item);
   void DoBehavior(C item);
}

abstract class A
{
    abstract void DoBehavior(IVisitor visitor);
}

class B : A
{
    override void DoBehavior(IVisitor visitor)
    {
       //can do some internal behavior here    
       visitor.DoBehavior(this); //external processing
    }
}

class C : A
{
    override void DoBehavior(IVisitor visitor)
    {
       //can do some internal behavior here   
       visitor.DoBehavior(this); //external processing
    }
}


class Manager: IVisitor //(or executor or whatever. The external processing class)
{

    public static void ProcessAll(List<A> items)
    {
        foreach(A item in items)
        {
            item.DoBehavior(this);
        }
    }

   void DoBehavior(B item)
   {

   }

   void DoBehavior(C item);
   { 

   }

}

皆さん、貢献してくれてありがとう。多くのことを学び、皆さんからいくつかの良いアイデアを得ました(同様の状況に直面した場合は、すべての回答を読む価値があります)。

于 2012-05-29T10:11:40.647 に答える
2

それがポリモーフィズムのすべてではないでしょうか。タイプによって動作が異なるメソッド。そして、これは「タイプスイッチ」よりも高速であると確信しています。
また、必要に応じて、(外部処理のために) 関数のオーバーロードを使用することもできます。以下のテスト プログラムを参照してください。

using System;
using System.Collections.Generic;

public class A
{
    public String Value
    {
        get;
        set;
    }

    public A()
    {
        Value = "A's value";
    }

    public virtual void Process()
    {
        // Do algorithm for type A
        Console.WriteLine("In A.Process()");
    }
}

public class B : A
{
    public int Health
    {
        get;
        set;
    }

    public B()
    {
        Value = "B's value";
        Health = 100;
    }

    public override void Process()
    {
        // Do algorithm for type B
        Console.WriteLine("In B.Process()");
    }
}

public static class Manager
{
    // Does internal processing
    public static void ProcessInternal(List<A> items)
    {
        foreach(dynamic item in items)
        {
            item.Process(); // Call A.Process() or B.Process() depending on type
            ProcessExternal(item);
        }
    }

    public static void ProcessExternal(A a)
    {
        Console.WriteLine(a.Value);
    }

    public static void ProcessExternal(B b)
    {
        Console.WriteLine(b.Health);
    }

    public static void Main(String[] args)
    {
        List<A> objects = new List<A>();
        objects.Add(new A());
        objects.Add(new B());
        ProcessInternal(objects);
    }
}

これは .Net 4.0 でのみ機能することに注意してください。

于 2012-05-28T22:51:55.740 に答える
1

簡単な解決策の 1 つは、基本クラスにクラス タイプを指定するフィールドを追加することです。

    class A
    {
        // Alternative
        string typeName =  this.GetType().Name;
        public virtual string TypeName { get { return typeName; } }

        public virtual string GetTypeName() { return "A"; }
    }

    class B : A
    {
        public override string GetTypeName() { return "B"; }
    }

    class C : A
    {
        public override string GetTypeName() { return "C"; }
    }


    class Executer
    {

        void ExecuteCommand(A val)
        {
            Console.WriteLine(val.GetType().Name);

            switch (val.GetTypeName())
            {
                case "A": DoSomethingA(val as A); break;
                case "B": DoSomethingB(val as B); break;
                case "C": DoSomethingC(val as C); break;
            }
        }

        private void DoSomethingC(C c)
        {
            throw new NotImplementedException();
        }

        private void DoSomethingB(B b)
        {
            throw new NotImplementedException();
        }

        private void DoSomethingA(A a)
        {
            throw new NotImplementedException();
        }

    }

文字列を実際に使用する必要はありませんが、同じ名前空間で同じ名前の 2 つのクラスを宣言できないという単純な理由から、整数を使用するよりもそのオプションを好みます。そのため、常に名前クラスを返す場合は、自動抗紛争メカニズム。

于 2012-05-28T22:38:17.653 に答える