具有继承专业化和专业方法的 Fluent Builder returns(奇怪的重复模板)
Fluent builder with inherited specialisation and specialised method returns (curiously recurring template)
我正在尝试编写其他开发人员可以流畅使用的数据管道构建器。如果我从预期结果的示例开始,这可能是最简单的:
var b = new Builder();
b.Batch().BatchedOperation().Unbatch().UnbatchedOperation().etc.
我已经能够创建一个抽象构建器,派生构建器可以使用奇怪的重复模板模式对其进行专门化。
我纠结的关键点是某些操作只能在某些其他操作之后才能进行。具体来说,管道中有两种主要类型的操作:对实例集起作用的操作,以及对单个实例起作用的操作。因此,只有在 Batched
的管道上执行 BatchedOperation
和在 Unbatched
.[=38= 的管道上执行 UnbatchedOperation
才有效]
为了下面的示例代码,我将管道在任何时候都视为两种形式之一:Foo
形式或 Bar
形式。这基本上等同于批处理或未批处理,但它将代码缩减到核心问题,而不会挂断批处理或未批处理的确切含义,并消除了混乱。
首先,假设我从这样的东西开始,基本的 CRTP:
public abstract class Builder<TBuilder> where TBuilder : Builder<TBuilder>
{
protected TBuilder builder;
internal Builder() => builder = (TBuilder)this;
public TBuilder Bar() => builder;
public TBuilder Foo() => builder;
}
我可以创建这个构建器的一些专业化,如下所示:
public class SpecialBuilder : Builder<SpecialBuilder>
{
public SpecialBuilder() : base() { }
public SpecialBuilder Special() => builder;
}
但是,问题是它允许我做类似的事情:
var b = new SpecialBuilder();
b.Foo().Foo().etc
这不好,因为管道一旦 Foo()
d 就不可能再 Foo()
它,因为它现在处于 Bar()
可用状态。需要明确的是,这会引发运行时错误,但不会在编译时捕获(或者,特别是通过智能感知)。
我可以使用接口限制管道操作的结果:
public interface IBar<T> { IFoo<T> Bar(); }
public interface IFoo<T> { IBar<T> Foo(); }
public abstract class Builder<TBuilder> : IFoo<TBuilder>, IBar<TBuilder>
where TBuilder : Builder<TBuilder>
{
protected TBuilder builder;
internal Builder() => builder = (TBuilder)this;
public IFoo<TBuilder> Bar() => builder;
public IBar<TBuilder> Foo() => builder;
}
但是,这会返回到不可继承的构建器,因为现在我的派生构建器一旦 Foo()
d 就无法运行。那时它不再是 Special()
d,因为它现在是 IBar
,而不是 SpecialBuilder
。
看来我需要额外的专用接口:
public interface ISpecialFoo<T> : IFoo<T> { T Special(); }
public interface ISpecialBar<T> : IBar<T> { T Special(); }
当然,摘要 Builder
仍然不能在 IFoo<TBuilder>
中指定 Bar()
return,因为那仍然不是 SpecialBuilder
,因此不能 Special()
d。所以看起来接口 return 类型本身 也 需要遵循奇怪的重复模板模式。
这是我的大脑开始痛的地方。我想也许是这样的:
public abstract class Builder<TBuilder, TFoo, TBar>
: IFoo<TBuilder>, IBar<TBuilder> // errors for both of these
where TBuilder : Builder<TBuilder, TFoo, TBar>, IFoo<TBuilder>, IBar<TBuilder>, TFoo, TBar
where TFoo : IFoo<TBuilder>
where TBar : IBar<TBuilder>
{
protected TBuilder builder;
internal Builder() => builder = (TBuilder)this;
public TFoo Bar() => builder;
public TBar Foo() => builder;
}
但这给了我两个对称错误,因为我尝试从上面指出的接口继承:
'Builder<TBuilder, TFoo, TBar>' does not implement interface member
IFoo.Foo()'. 'Builder<TBuilder, TFoo, TBar>.Foo()' cannot
implement 'IFoo.Foo()' because it does not have
thematching return type of 'IBar'
'Builder<TBuilder, TFoo, TBar>' does not implement interface member
'IBar.Bar()'. 'Builder<TBuilder, TFoo, TBar>.Bar()' cannot
implement 'IBar.Bar()' because it does not have the
matching return type of 'IFoo'.
这甚至可以做到吗?我真的很想为这段代码的用户提供编译时帮助,例如,intellisense 仅显示构建器状态的有效操作。但是他们的收获显然是我在这里的痛苦。
这是一个完整的控制台应用程序,展示了我得到的最接近的应用程序:
public interface ISpecial { SpecialBuilder Special(); }
public interface ISpecialFoo : IFoo<SpecialBuilder>, ISpecial { }
public interface ISpecialBar : IBar<SpecialBuilder>, ISpecial { }
public interface IBar<T> { IFoo<T> Bar(); }
public interface IFoo<T> { IBar<T> Foo(); }
internal class Program
{
private static void Main(string[] args)
{
var b = new SpecialBuilder();
b.Special().Foo().Special().Bar();
}
}
public abstract class Builder<TBuilder, TFoo, TBar>
where TBuilder : Builder<TBuilder, TFoo, TBar>, TFoo, TBar
where TFoo : IFoo<TBuilder>
where TBar : IBar<TBuilder>
{
protected TBuilder builder;
internal Builder() => builder = (TBuilder)this;
public TFoo Bar() => builder;
public TBar Foo() => builder;
}
public class SpecialBuilder : Builder<SpecialBuilder, ISpecialFoo, ISpecialBar>, ISpecialFoo, ISpecialBar
{
public SpecialBuilder() : base() { }
public SpecialBuilder Special() => builder;
}
只需在各处悬挂类型信息即可。
// I moved the generic interfaces inside the CRTP base
// to save some typing for the CRTP consumers.
// You can also move IState1 and IState2 outside, and
// they will need generic parameters TIState1, TIState2.
public abstract class Builder<TBuilder, TIState1, TIState2>
: Builder<TBuilder, TIState1, TIState2>.IState1,
Builder<TBuilder, TIState1, TIState2>.IState2
// This constraint surprisingly (or not) compiles.
where TBuilder : Builder<TBuilder, TIState1, TIState2>, TIState1, TIState2
where TIState1 : Builder<TBuilder, TIState1, TIState2>.IState1
where TIState2 : Builder<TBuilder, TIState1, TIState2>.IState2
{
public interface IState1 { TIState2 OpState1(); }
public interface IState2 { TIState1 OpState2(); }
private TBuilder that;
protected Builder() { that = (TBuilder)this; }
public TIState2 OpState1() { return that; }
public TIState1 OpState2() { return that; }
}
public sealed class MyBuilder
: Builder<MyBuilder, MyBuilder.IMyState1, MyBuilder.IMyState2>,
MyBuilder.IMyState1, MyBuilder.IMyState2
{
public interface IMySpecial { MyBuilder OpSpecial(); }
public interface IMyState1 : IState1, IMySpecial { }
public interface IMyState2 : IState2, IMySpecial { }
public MyBuilder OpSpecial() { return this; }
}
现在,如果您尝试 new MyBuilder().OpState1().
,因为 return 类型是 IMyState2
,您将只会看到 OpSpecial
、OpState2
和 object
方法。
但是,使用接口限制方法可见性的方法对性能有严重的影响,因为接口调度非常慢。
我正在尝试编写其他开发人员可以流畅使用的数据管道构建器。如果我从预期结果的示例开始,这可能是最简单的:
var b = new Builder();
b.Batch().BatchedOperation().Unbatch().UnbatchedOperation().etc.
我已经能够创建一个抽象构建器,派生构建器可以使用奇怪的重复模板模式对其进行专门化。
我纠结的关键点是某些操作只能在某些其他操作之后才能进行。具体来说,管道中有两种主要类型的操作:对实例集起作用的操作,以及对单个实例起作用的操作。因此,只有在 Batched
的管道上执行 BatchedOperation
和在 Unbatched
.[=38= 的管道上执行 UnbatchedOperation
才有效]
为了下面的示例代码,我将管道在任何时候都视为两种形式之一:Foo
形式或 Bar
形式。这基本上等同于批处理或未批处理,但它将代码缩减到核心问题,而不会挂断批处理或未批处理的确切含义,并消除了混乱。
首先,假设我从这样的东西开始,基本的 CRTP:
public abstract class Builder<TBuilder> where TBuilder : Builder<TBuilder>
{
protected TBuilder builder;
internal Builder() => builder = (TBuilder)this;
public TBuilder Bar() => builder;
public TBuilder Foo() => builder;
}
我可以创建这个构建器的一些专业化,如下所示:
public class SpecialBuilder : Builder<SpecialBuilder>
{
public SpecialBuilder() : base() { }
public SpecialBuilder Special() => builder;
}
但是,问题是它允许我做类似的事情:
var b = new SpecialBuilder();
b.Foo().Foo().etc
这不好,因为管道一旦 Foo()
d 就不可能再 Foo()
它,因为它现在处于 Bar()
可用状态。需要明确的是,这会引发运行时错误,但不会在编译时捕获(或者,特别是通过智能感知)。
我可以使用接口限制管道操作的结果:
public interface IBar<T> { IFoo<T> Bar(); }
public interface IFoo<T> { IBar<T> Foo(); }
public abstract class Builder<TBuilder> : IFoo<TBuilder>, IBar<TBuilder>
where TBuilder : Builder<TBuilder>
{
protected TBuilder builder;
internal Builder() => builder = (TBuilder)this;
public IFoo<TBuilder> Bar() => builder;
public IBar<TBuilder> Foo() => builder;
}
但是,这会返回到不可继承的构建器,因为现在我的派生构建器一旦 Foo()
d 就无法运行。那时它不再是 Special()
d,因为它现在是 IBar
,而不是 SpecialBuilder
。
看来我需要额外的专用接口:
public interface ISpecialFoo<T> : IFoo<T> { T Special(); }
public interface ISpecialBar<T> : IBar<T> { T Special(); }
当然,摘要 Builder
仍然不能在 IFoo<TBuilder>
中指定 Bar()
return,因为那仍然不是 SpecialBuilder
,因此不能 Special()
d。所以看起来接口 return 类型本身 也 需要遵循奇怪的重复模板模式。
这是我的大脑开始痛的地方。我想也许是这样的:
public abstract class Builder<TBuilder, TFoo, TBar>
: IFoo<TBuilder>, IBar<TBuilder> // errors for both of these
where TBuilder : Builder<TBuilder, TFoo, TBar>, IFoo<TBuilder>, IBar<TBuilder>, TFoo, TBar
where TFoo : IFoo<TBuilder>
where TBar : IBar<TBuilder>
{
protected TBuilder builder;
internal Builder() => builder = (TBuilder)this;
public TFoo Bar() => builder;
public TBar Foo() => builder;
}
但这给了我两个对称错误,因为我尝试从上面指出的接口继承:
'Builder<TBuilder, TFoo, TBar>' does not implement interface member IFoo.Foo()'. 'Builder<TBuilder, TFoo, TBar>.Foo()' cannot implement 'IFoo.Foo()' because it does not have thematching return type of 'IBar'
'Builder<TBuilder, TFoo, TBar>' does not implement interface member 'IBar.Bar()'. 'Builder<TBuilder, TFoo, TBar>.Bar()' cannot implement 'IBar.Bar()' because it does not have the matching return type of 'IFoo'.
这甚至可以做到吗?我真的很想为这段代码的用户提供编译时帮助,例如,intellisense 仅显示构建器状态的有效操作。但是他们的收获显然是我在这里的痛苦。
这是一个完整的控制台应用程序,展示了我得到的最接近的应用程序:
public interface ISpecial { SpecialBuilder Special(); }
public interface ISpecialFoo : IFoo<SpecialBuilder>, ISpecial { }
public interface ISpecialBar : IBar<SpecialBuilder>, ISpecial { }
public interface IBar<T> { IFoo<T> Bar(); }
public interface IFoo<T> { IBar<T> Foo(); }
internal class Program
{
private static void Main(string[] args)
{
var b = new SpecialBuilder();
b.Special().Foo().Special().Bar();
}
}
public abstract class Builder<TBuilder, TFoo, TBar>
where TBuilder : Builder<TBuilder, TFoo, TBar>, TFoo, TBar
where TFoo : IFoo<TBuilder>
where TBar : IBar<TBuilder>
{
protected TBuilder builder;
internal Builder() => builder = (TBuilder)this;
public TFoo Bar() => builder;
public TBar Foo() => builder;
}
public class SpecialBuilder : Builder<SpecialBuilder, ISpecialFoo, ISpecialBar>, ISpecialFoo, ISpecialBar
{
public SpecialBuilder() : base() { }
public SpecialBuilder Special() => builder;
}
只需在各处悬挂类型信息即可。
// I moved the generic interfaces inside the CRTP base
// to save some typing for the CRTP consumers.
// You can also move IState1 and IState2 outside, and
// they will need generic parameters TIState1, TIState2.
public abstract class Builder<TBuilder, TIState1, TIState2>
: Builder<TBuilder, TIState1, TIState2>.IState1,
Builder<TBuilder, TIState1, TIState2>.IState2
// This constraint surprisingly (or not) compiles.
where TBuilder : Builder<TBuilder, TIState1, TIState2>, TIState1, TIState2
where TIState1 : Builder<TBuilder, TIState1, TIState2>.IState1
where TIState2 : Builder<TBuilder, TIState1, TIState2>.IState2
{
public interface IState1 { TIState2 OpState1(); }
public interface IState2 { TIState1 OpState2(); }
private TBuilder that;
protected Builder() { that = (TBuilder)this; }
public TIState2 OpState1() { return that; }
public TIState1 OpState2() { return that; }
}
public sealed class MyBuilder
: Builder<MyBuilder, MyBuilder.IMyState1, MyBuilder.IMyState2>,
MyBuilder.IMyState1, MyBuilder.IMyState2
{
public interface IMySpecial { MyBuilder OpSpecial(); }
public interface IMyState1 : IState1, IMySpecial { }
public interface IMyState2 : IState2, IMySpecial { }
public MyBuilder OpSpecial() { return this; }
}
现在,如果您尝试 new MyBuilder().OpState1().
,因为 return 类型是 IMyState2
,您将只会看到 OpSpecial
、OpState2
和 object
方法。
但是,使用接口限制方法可见性的方法对性能有严重的影响,因为接口调度非常慢。