“返回这个”,使用 C# 中构建器的子类型

本文关键字:构建 类型 使用 返回 返回这个 | 更新日期: 2023-09-27 17:56:09

我可以让这个C#代码做我想做的事,并让ShapeBuilder.color返回这个派生的子类型吗?

class Shape {
    public String Color { get; private set; }
    public Shape(String color) {
        this.Color = color;
    }
}
class Circle : Shape {
    public int Radius { get; private set; }
    public Circle(String color, int radius) : base(color) {
        this.Radius = radius;
    }
    public override String ToString() {
        return String.Format("Circle c={0} r={1}", this.Color, this.Radius);
    }
}
class ShapeBuilder {
    // How can this function return the subtype, not just a ShapeBuilder?
    public ShapeBuilder color(String color) {
        this._color = color;
        return this;        
    }
    protected String _color;
}
class CircleBuilder : ShapeBuilder {
    public CircleBuilder radius(int radius) {
        this._radius = radius;
        return this;        
    }
    public Circle build() {
        return new Circle(_color, _radius);
    }
    protected int _radius;
}
class Program {
    static void Main() {
        // This works.
        Circle c1 = new CircleBuilder().radius(5).build();
        Console.WriteLine("c1: " + c1);
        // This won't compile, because color returns ShapeBuilder rather than CircleBuilder.
        Circle c2 = new CircleBuilder().color("blue").build();
        Console.WriteLine("c2: " + c2);
    }
}

在 Scala 中,我会这样做:

case class Shape(val color: String)
class Circle(color: String, radius: Int) extends Shape(color)
class ShapeBuilder {
  def color(color: String): this.type = {
    this.color = color
    this
  }
  def build() = new Shape(color)
  protected var color: String = ""
}
class CircleBuilder extends ShapeBuilder {
  def radius(radius: Int): this.type = {
    this.radius = radius
    this
  }
  override def build() = new Circle(color, radius)
  protected var radius: Int = 0
}
object BuilderTest extends App {
    val c = new CircleBuilder().color("blue").radius(5).build();
}

“返回这个”,使用 C# 中构建器的子类型

它可以通过泛型来完成。Scala 版本是最好的,因为它根本不需要泛型来为派生类型的this提供this.type。下面是 C# 版本:

class Shape {
    public String Color { get; private set; }
    public Shape(String color) {
        this.Color = color;
    }
public override String ToString() {
        return String.Format("Shape c={0}", this.Color);
    }
}
class Circle : Shape {
    public int Radius { get; private set; }
    public Circle(String color, int radius) : base(color) {
        this.Radius = radius;
    }
    public override String ToString() {
        return String.Format("Circle c={0} r={1}", this.Color, this.Radius);
    }
}
interface Builder<T> {
    T build();
}
abstract class AbstractShapeBuilder<B, T> : Builder<T> where B : AbstractShapeBuilder<B, T> where T : Shape {
    abstract protected B getThis();
    abstract public T build();
    public B color(String color) {
        this._color = color;
        return getThis();        
    }
    protected String _color;
}
class ShapeBuilder : AbstractShapeBuilder<ShapeBuilder, Shape> {
    override protected ShapeBuilder getThis() { return this; }
    override public Shape build() {
        return new Shape(_color);
    }
}
abstract class AbstractCircleBuilder<B, T> : AbstractShapeBuilder<B, T> where B : AbstractCircleBuilder<B, T> where T : Circle {
    public B radius(int radius) {
        this._radius = radius;
        return getThis();        
    }
    protected int _radius;
}
class CircleBuilder : AbstractCircleBuilder<CircleBuilder, Circle> {
    override protected CircleBuilder getThis() { return this; }
    override public Circle build() {
        return new Circle(_color, _radius);
    }
}
class Program {
    static void Main() {
        Console.WriteLine("CircleBuilder: v1");
        Shape s1 = new ShapeBuilder().color("yellow").build();
        Console.WriteLine("s1: " + s1);
        Circle c1 = new CircleBuilder().color("blue").radius(3).build();
        Console.WriteLine("c1: " + c1);
    }
}

仅供参考,这是Java版本:

public class BuilderTest {
    public static class Shape {
        public Shape(String color) {
            this.color = color;
        }
        @Override public String toString() {
            return String.format("shape. color=%s.", color);
        }
        public final String color;
    }
    public static class Circle extends Shape {
        public Circle(String color, int radius) {
            super(color);
            this.radius = radius;
        }
        @Override public String toString() {
            return String.format("circle. color=%s. radius=%d.", color, radius);
        }
        public final int radius;
    }
    @FunctionalInterface
    public static interface Builder<T> {
        public T build();
    }
    public static abstract class AbstractShapeBuilder<B extends AbstractShapeBuilder<B>> {
        protected abstract B getDerivedThis();
        public B color(String color) {
            this.color = color;
            return getDerivedThis();
        }
        String color;
    }
    public static class ShapeBuilder extends AbstractShapeBuilder<ShapeBuilder> implements Builder<Shape> {
        @Override protected ShapeBuilder getDerivedThis() { return this; }
        @Override public Shape build() {
            return new Shape(color);
        }
    }
    public static abstract class AbstractCircleBuilder<B extends AbstractCircleBuilder<B>> extends AbstractShapeBuilder<B> {
        public B radius(int radius) {
            this.radius = radius;
            return getDerivedThis();
        }
        int radius;
    }
    public static class CircleBuilder extends AbstractCircleBuilder<CircleBuilder> implements Builder<Circle> {
        @Override protected CircleBuilder getDerivedThis() { return this; }
        @Override public Circle build() { return new Circle(color, radius); }
    }
    public static void main(String[] args) throws Exception {
        Shape s = new ShapeBuilder().color("red").build();
        System.out.println("s = " + s);
        Circle c = new CircleBuilder().color("blue").radius(5).build();
        System.out.println("c = " + c);
    }
}
public class Shape {
  public String Color { get; private set; }
  public Shape(String color) {
    this.Color = color;
  }
}
public class Circle : Shape {
  public int Radius { get; private set; }
  public Circle(String color, int radius)
    : base(color) {
    this.Radius = radius;
  }
  public override String ToString() {
    return String.Format("Circle c={0} r={1}", this.Color, this.Radius);
  }
}
public class ShapeBuilder<TBuilder, TShape> 
    where TBuilder : ShapeBuilder<TBuilder, TShape> 
    where TShape : Shape {
  // How can this function return the subtype, not just a ShapeBuilder?
  public TBuilder color(String color) {
    this._color = color;
    return (TBuilder)this;
  }
  public virtual TShape build() {
    return default(TShape);
  }
  protected String _color;
}
public class CircleBuilder : ShapeBuilder<CircleBuilder, Circle> {
  public CircleBuilder radius(int radius) {
    this._radius = radius;
    return this;
  }
  public override Circle build() {
    return new Circle(_color, _radius);
  }
  protected int _radius;
}

简单和最惯用的方法是使用new方法。

class ShapeBuilder {
    // How can this function return the subtype, not just a ShapeBuilder?
    public ShapeBuilder color(String color) {
        this._color = color;
        return this;        
    }
    protected String _color;
}
class CircleBuilder : ShapeBuilder {
    public new CircleBuilder color(String color) {
        return (CircleBuilder)base.color(color)
    }
}

为什么这是一个好方法?

在这种情况下,泛型是不好的,因为它们使编写在不同 ShapeBuilder 上多态操作的代码变得更加复杂。像这样的泛型基类往往会强制调用代码也是泛型的,而没有重大好处。

ShapeBuilder s = new CircleBuilder(); s.color(Red);AbstractShapeBuilder<Circle, CircleBuilder> s = new CircleBuilder(); s.color(Red);更容易使用和作为参数传递

像这样使用 new 关键字可以让子类型的用户获得了解特定类型的好处,还允许超类型的用户多态操作,而不会给调用方带来任何额外的复杂性。这种技术在.NET Framework中到处都是,你甚至没有注意到它,因为它非常直观 - 这就是SqlConnection.CreateCommand()和OleDbConnection.CreateCommand()实现相同基本方法的方式,即使它们具有不同的返回类型。

也许 Jon Skeet 不喜欢使用 new ,但我觉得通用版本明显更丑陋,可读性较差,使用起来更复杂。

请参阅: 如何在 C# 中子类的重写方法中返回子类型?

也许您可以使用泛型?

ShapeBuilder<T>

参见 Stormenet 中的示例。

不使用泛型,你就不走运了,所以你的回答基本上是"否"。

因此,您只是在寻找可能的替代方案:

另一种选择(除了模板)是使用对象初始值设定项:

http://msdn.microsoft.com/en-us/library/bb397680.aspx

这也不是你想要的,但你不能总是得到你想要的:)

    StudentName student2 = new StudentName
    {
        FirstName = "Craig",
        LastName = "Playstead",
    };