Search code examples
c#vb.nettype-inferencefluent-interface

Using Type Inference with fluent interfaces


I have a class /interface hierarchy. On the interface side I have

IQuery
  ISelect      (inherits IQuery)
  IUpdate      (inherits IQuery)
  etc

On the class side I have

QueryBase       (implements IQuery)
  SelectQuery   (implements ISelect)
  UpdateQuery   (implements IUpdate)
  etc

Obviously, for example, both Update and Select classes share a WHERE clause but only a Select has GROUP BY functionality so ideally if an Update Query is being creating, the fluent interface will not give access to GROUP BY functionality but would do if a SelectQuery was being created.

eg in fluent interface terms

  var/Dim select = New SelectQuery()        <- returns ISelect explicit
                          .AddColumn(....)  <- returns ISelect explicit
                          .AddWhere(....)   <- returns ISelect inferred
                          .AddGroupBy(....) <- returns ISelect explicit

  var/Dim update = New UpdateQuery()        <- returns IUpdate explicit
                          .AddSet(....)     <- returns IUpdate explicit
                          .AddWhere(....)   <- returns IUpdate inferred

I am unsure how to implement the AddWhere function.

Previously I had declared the AddWhere function in the IQuery interface as

Function AddWhere(ByVal condition As ICriterion) As IQuery

IQuery AddWhere(ICriterion condition)

but because it was returning an IQuery, I was losing the benefits of the type inference and so as soon as the fluent interface had cast to the IQuery, if it was a Select query being created, I would no longer have access to, eg, the AddGroupBy method.

So I have tried to implement it as an Extension Method with generics

<Extension>
Public Function AddWhere(Of T As IQuery)(Byval this as T, Byval condition as Condition) as T
    this.SetWhere(condition)
    Return Me
End Function

public T AddWhere<T>(T @this, Condition condition) where T : IQuery
{
    @this.SetWhere(condition);
    return this;
}

with a Friend (internal) method, SetWhere, on QueryBase to permit me to update the WHERE clause. However because the generic is constrained to IQuery, it won't find the SetWhere. However, if I constrain as QueryBase, then, obviously, the compiler throws wobblies saying that the ISelect can't find an AddWhere method.

I'm thinking that I haven't quite got the inheritence chain or interface implementations quite right for what I'm trying to achieve.

(I hope that is clear!!)

I'd be grateful if someone could suggest either where I am going wrong in terms of the extension method implementation, or how I should better structure my class/interface hierarchy.


Solution

  • Public Interface IQuery
        Function AddWhere() As IQuery
    End Interface
    
    Public Interface IUpdate : Inherits IQuery
        Overloads Function AddWhere() As IUpdate
    End Interface
    
    Public Interface ISelect : Inherits IQuery
        Overloads Function AddWhere() As ISelect
        Function AddGroupBy() As ISelect
    End Interface
    
    Public Class QueryBase : Implements IQuery
        Public Function AddWhere() As IQuery Implements IQuery.AddWhere
            ''...
            Return Me
        End Function
    End Class
    
    Public Class UpdateQuery : Inherits QueryBase : Implements IUpdate
        Public Shadows Function AddWhere() As IUpdate Implements IUpdate.AddWhere
            MyBase.AddWhere()
            Return Me
        End Function
    End Class
    
    Public Class SelectQuery : Inherits QueryBase : Implements ISelect
        Public Shadows Function AddWhere() As ISelect Implements ISelect.AddWhere
            MyBase.AddWhere()
            Return Me
        End Function
        Public Function AddGroupBy() As ISelect Implements ISelect.AddGroupBy
            ''...
            Return Me
        End Function
    End Class