根据层次结构中的类型以最佳方式优雅地执行某些行为
本文关键字:执行 方式优 最佳 层次结构 类型 | 更新日期: 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,你就有一个自动反冲突机制。