When designing fluent APIs, one issue that comes up is partial type inference. If a method has two type parameters, there is no way to call it and only specify one of the type parameters (and leave the other inferred by the compiler)
For example, suppose we are creating a type-safe wrapper around a parameterized SqlCommand.
Ideally, it would be called like this:
using(DbConnection connection = ...) { var result = connection.ExecuteScalar<int>( "SELECT COUNT(*) FROM TableName WHERE Modified > someDate", new { someDate } ); }
Where the generic parameter specifies the return type.
In order to implement this efficiently, one would create methods at runtime which add DbParameters for each property in the anonymous type, and store them in a static generic class.
It would look something like this:
static class Extensions { public static void AddParams<TParam>(this IDbCommand command, TParam parameters) where TParam : class { if (parameters != null) ParamAdders<TParam>.Adder(command, parameters); } static class ParamAdders<TParam> where TParam : class { public delegate void ParamAdder(IDbCommand command, TParam parameters); public static readonly ParamAdder Adder = CreateParamAdder(); private static ParamAdder CreateParamAdder() { return ...; } } }
However, that requires that the ExecuteScalar extension method take TParam as a generic parameter. Since anonymous types can only be passed as generic parameters via type inference, this makes it impossible to pass the return type as a generic parameter.
To fix this issue, we can split the generic parameters across two methods. We can change the extension method to take a single generic parameter, and return a generic class with a method that takes the other generic parameter.
For example:
static class BetterExtensions { public static SqlStatement<T> Sql<T>(this IDbConnection conn, string sqlText) { return new SqlStatement<T>(conn, sqlText); } } class SqlStatement<TReturn> : IHideObjectMembers { public SqlStatement(IDbConnection connection, string sql) { Connection = connection; Sql = sql; } public IDbConnection Connection { get; private set; } public string Sql { get; private set; } public TReturn Execute() { return Execute<object>(null); } public TReturn Execute<TParam>(TParam parameters) where TParam : class { return ...; } }
I use an IHideObjectMembers interface to hide the methods inherited from Object from IntelliSense. Note that the interface must be defined in an assembly outside of your solution. (or, to be more precise, that isn’t a Project reference)
This version would be called like this:
using(IDbConnection connection = null) { var result = connection.Sql<int>( "SELECT COUNT(*) FROM TableName WHERE Modified > someDate" ).Execute(new { someDate }); }
The return type is specified explicitly in the call to Sql<T>()
, and the parameter type is passed implicitly to Execute<TParam>()
.