I'm using a C# data visualization library which makes heavy use of generics. Our application needs to build these charts programtically, including changing the series of the chart at runtime. The code looks like this:
builder.Series(series =>
{
if (seriesDefinition.Type == ChartType.Bar) {
series.Bar(data).Name(seriesDefinition.Name);
}
else if (seriesDefinition.Type == ChartType.Area) {
series.Area(data).Name(seriesDefinition.Name);
}
})
Where the Bar()
and Area()
calls are library methods, and the seriesDefinition
object is our own class which contains configuration information about the series we want to create.
Now, our series definition object is going to have many properties besides just the name, and all of them will need to be applied to the series in the builder. To avoid a massive amount of code duplication, we want to store the result of the Bar()
or Area()
etc. call, so that we can apply the rest of the configurations once outside of the if-else blocks. To do that, we need to declare its specific type. Working backward from the derived return types of those library methods, we get to this parent type which has the methods we need:
public abstract class ChartSeriesBuilderBase<TSeries, TSeriesBuilder> : IHideObjectMembers
where TSeries : IChartSeries
where TSeriesBuilder : ChartSeriesBuilderBase<TSeries, TSeriesBuilder>
{
}
Now I need to declare it with appropriate type arguments:
builder.Series(series =>
{
ChartSeriesBuilderBase<IChartSeries, ???> seriesBuilder = null; // <--
if (seriesDefinition.Type == ChartType.Bar) {
seriesBuilder = series.Bar(data);
}
else if (seriesDefinition.Type == ChartType.Area) {
seriesBuilder = series.Area(data);
}
seriesBuilder.Name(seriesDefinition.Name);
})
What can I replace ???
with that satisfies this type declaration?
Edit: Here is the class hierarchy starting with the return types of Bar()
or Area()
.
// Area() returns this; Bar() returns ChartBarSeriesBuilder
public class ChartAreaSeriesBuilder<T> :
ChartAreaSeriesBuilderBase<IChartAreaSeries, ChartAreaSeriesBuilder<T>>
where T : class
{
}
T
is the type of the data item for the series, which I have control over. It's inferred when I pass in an object of that type to Area()
.
// or ChartBarSeriesBuilderBase
public abstract class ChartAreaSeriesBuilderBase<TSeries, TSeriesBuilder> :
ChartSeriesBuilderBase<TSeries, TSeriesBuilder>
where TSeries : IAreaSeries
where TSeriesBuilder : ChartAreaSeriesBuilderBase<TSeries, TSeriesBuilder>
{
}
and finally
public abstract class ChartSeriesBuilderBase<TSeries, TSeriesBuilder> : IHideObjectMembers
where TSeries : IChartSeries
where TSeriesBuilder : ChartSeriesBuilderBase<TSeries, TSeriesBuilder>
{
}