根据层次结构中的类型以最佳方式优雅地执行某些行为

本文关键字:执行 方式优 最佳 层次结构 类型 | 更新日期: 2023-09-27 18:35:38

我正在尝试优化代码的某个部分,这恰好处于紧密的性能循环中。主要是我尝试学习新事物,以便将来应用。我的实现非常冗长,所以我将给出一个我试图实现的目标的一般示例。

我的问题与此有关:C#"是"运算符性能,尤其是所选答案。

假设我有一个A类。我还有一个B类,它派生自A。我有一个类型 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 的类型列表。另外,如果您觉得我没有提供足够的代码来澄清情况,我很乐意扩展。

在我上面发布的问题:C#'is'is'运算符性能)中,我了解到我可以更改结构以使用"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类(或其子类)的行为,而是一些外部类的行为/行为(每种类型的行为都不同)。如果没有更好的方法,我会强烈考虑按照我现在所解释的那样实施它 - 但我希望对此有一些专家意见。

我试图保持简短,可能遗漏了太多细节。如果是这种情况,请告诉我。

根据层次结构中的类型以最佳方式优雅地执行某些行为

我强烈建议你避免使用"if..否则如果..否则如果.." 通过编程到接口而不是引用具体类来路径。

若要实现此目的,首先使Process()方法不了解其参数的类型。可能该参数最终会成为类似 IDoSomething 的接口。

接下来,实现Process(),以便它不会直接调用DoSomething()。您必须将DoSomething()分解为较小的代码块,这些代码块将被移动到IDoSomething方法的特定实现中。Process()方法将盲目调用这些方法 - 换句话说,将IDoSomething协定应用于某些数据。

DoSomething()越复杂,这可能会很烦人,但你会有一个更好的关注点分离,并且会"打开"Process()到任何IDoSomething兼容的类型,甚至不用再写一个else

这不就是多态性的全部内容吗?根据其类型具有不同行为的方法。而且我相当确定这将比"类型开关"更快。
如果需要,您还可以使用函数重载(用于外部处理),请参阅下面的测试程序:

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 !

对于我发现的情况,最好的解决方案是使用双重调度/访客模式。我描述了一种情况,其中基类 A 是抽象的,具体类 B 和 C 继承自 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);
   { 
   }
}

谢谢大家的贡献。学到了很多东西,并从大家那里得到了一些好主意(如果您遇到类似的情况,阅读所有答案是值得的)。

一个简单的解决方案是在基类中添加一个字段,指定类类型。

    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 个同名的类,因此如果你总是返回 name class,你就有一个自动反冲突机制。