多态重载 - 如何强制抽象类型作为其派生类型之一"act"以用于重载参数?


DoSomething(Car car);
DoSomething(Bike bike);
public class Car : Vehicle {}
public class Bike : Vehicle {}
public abstract class Vehicle {}

void run(Vehicle vehicle) {
    DoSomething(vehicle);
}

这感觉就像一个简单的问题,但我遇到了问题。滴定(车辆(不存在,因此,即使车辆"保证"是汽车或自行车,dosomething(车辆(也会出错。我如何说服编译器"车辆"是自行车或汽车,以便可以运行滴定?

当然,我可以沿着

的另一种方法
DoSomething(Vehicle vehicle)
{
    if(vehicle is Car) ... etc
}

,但肯定有一种更干净的方法?

编辑/Clarity

将此代码放在经理类中,而不是在车辆类中存在dosomething((,这是每辆车都需要访问程序的不同部分。例如:

DoSomething(Car car) {
    motorwayInfo.CheckMotorwayStatus();
}
DoSomething(Bike bike) {
    cycleInfo.CheckCyclePathStatus();
}

不确定这个类比是否真的在我的特定问题上很好,哈哈 - 但基本上,我不希望汽车对Cycleinfo有任何参考,也不希望汽车对RoidwayWayInfo有任何参考。但是,将滴定物放入车辆上基本上意味着其参数需要:

DoSomething(CycleInfo cycleInfo, MotorwayInfo motorwayInfo)

DoSomething(InfoManager infoManager)

我都不知道每个子类型都只能使用特定的信息对象。我要这一切都错了吗?

这里的真正问题是 - 您期望发生什么方法的参数?如果是这样,则取决于参数的混凝土(sub(类型,那么该行为的适当位置是参数基类的虚拟(甚至是抽象(成员-Vehicle

class Vehicle
{
    public abstract void Behave();
}
class Car : Vehicle
{
    public override void Behave()
    {
        // Do something specific to a car
    }
}
class Bike : Vehicle
{
    public override void Behave()
    {
        // Do something specific to a bike
    }
}
...
void Run(Vehicle vehicle)
{
    vehicle.Behave();
}

您可以从此代码中看到,我恢复了角色。Run函数是不负责知道具体参数应如何行为的负责人。取而代之的是,每个混凝土对象都通过Vehicle参数必须知道如何表现。那是正确的多态性。

关于Run方法,其有关参数的所有职责均应与所有参数对象的常见类型有关,这就是基类Vehicle。在这方面,该方法可以访问基类定义的成员,或将对象插入集合等等。

void Run(Vehicle vehicle)
{
    vehicle.Behave();
    List<Vehicle> list = ...
    list.Add(vehicle);
}

所需的内容称为double Dispatch,并且不受C#编译器的本地支持。

您可以通过一些重构来实现它:

public void DoSomething(Car car) {}
public void DoSomething(Bike bike) {}
public abstract class Vehicle
{
    // ...
    public abstract void CallDoSomething();
}
public class Car : Vehicle
{
    public override void CallDoSomething()
    {
        DoSomething(this);
    }
}
public class Bike : Vehicle
{
    public override void CallDoSomething()
    {
        DoSomething(this);
    }
}

或,如果您不关心性能惩罚,则可以使用dynamic关键字将过载分辨率推迟到运行时,然后选择与类型匹配的最合适的方法:

void run(dynamic vehicle)
{
    DoSomething(vehicle);
}

请参阅MSDN

车辆被"保证"为汽车或自行车

那不是真的。如果所有汽车都是车辆,并且所有自行车都是车辆,那并不意味着所有车辆都是自行车或汽车。子类型不起作用。

我该如何说服编译器"车辆"是自行车或汽车,以便可以运行吗?

做到这一点的唯一方法是铸造,您已经提到了:

if (vehicle is Car) (vehicle as Car).CheckMotorwayStatus();
else if (vehicle is Bike) (vehicle as Bike).CheckCyclePathStatus();

,但肯定有一种更干净的方法?

Zoran的答案建议的方法是更清洁的方法。

我不希望汽车对CycleInfo有任何参考,也不希望自行车提及RoidwayInfo。

有了一些想法,即使您的设置稍微更复杂,您也可以使用它。例如:

public abstract class Vehicle
{
    public abstract void PrintRouteStatus();
}
public class MotorwayInfo
{
}
public class CycleInfo
{
}
public class Car : Vehicle
{
    // probably pass this in via a constructor.
    public MotorwayInfo _motorwayInfo = new MotorwayInfo();
    public override void PrintRouteStatus()
    {
        Console.WriteLine(_motorwayInfo);
    }
}
public class Bike : Vehicle
{
    // probably pass this in via a constructor.
    public CycleInfo _cycleInfo = new CycleInfo();
    public override void PrintRouteStatus()
    {
        Console.WriteLine(_cycleInfo);
    }
}

它可以在所有自行车的信息中都没有意识到,反之亦然。

public static void Main()
{
    var p = new Program();
    p.DoSomething(new Car());
    p.DoSomething(new Bike());
}
public void DoSomething(Vehicle v)
{
    v.PrintRouteStatus();
}

我感到惊讶的是,这些答案都不包括通用或接口。您可以扭转这一点并创建一个接受的实例。

public abstract class Vehicle {}
public class Car : Vehicle {}
public class Bike : Vehicle {}
public void DoSomething<T>(T _vehicle) where T : Vehicle
{
    // _vehicle can be an instance of Car or Bike
    if (_vehicle is Car _car)
        motorwayInfo.CheckMotorwayStatus();
    if (_vehicle is Bike _bike)
        cycleInfo.CheckCyclePathStatus();
}

您可以进一步迈出一步并实现IVehicle接口。

fwiw,我写了我创造的内容为" DoubleDisPatchObject",以帮助解决此类问题 - 值得注意的是,

1(不丢失类型安全性(或诉诸动态关键字的使用,无论是在呼叫站点还是卡利亚(,

2(在基本类的选择上没有负担(而是简单的组成模式就足够(,

和3(仅产生几行样板。

这是您的场景的样子:

public class VehicleManager
{
  // boilerplate start
  private DoubleDispatchObject dispatch;
  public void DoSomething(Vehicle vehicle) =>
    this.EnsureThreadSafe(ref dispatch)
    .Via(nameof(DoSomething), vehicle, () => throw new NotImplementedException());
  // boilerplate end
  public void Run(Vehicle vehicle) =>
    DoSomething(vehicle);
  public void DoSomething(Car car) =>
    Console.WriteLine("Doing something with a car...");
  public void DoSomething(Bike bike) =>
    Console.WriteLine("Doing something with a bike...");
}
public abstract class Vehicle { }
public class Car : Vehicle { }
public class Bike : Vehicle { }
class MainClass {
  public static void Main (string[] args) {
    var manager = new VehicleManager();
    manager.Run(new Car());
    manager.Run(new Bike());
    Console.WriteLine("Done.");
  }
}

在repl.it上:

https://repl.it/@ysharp_design/so48975551

另请参阅此相关的拉请请求(有关单位测试(:

https://github.com/waf/multipledispatchbenchs/pull/1/files

'hth

最新更新