Smudge202
Smudge202

Reputation: 4687

Refactoring to get inheritance/interfaces/convariance correct

EDIT: I've added the C# equivilents to appeal to a wider audience - hope that's ok

VS2008 - .Net 3.5 (I therefore don't get convariance/contravariance support, as far as I know?)

It seems this is likely to require convariance/contravariance and therefore .Net 4, so to prevent the question being impossible, feel free to answer for .Net4 though, I would prefer a simple refactoring if it's available.

This should be simple but for some reason my brain just isn't up to the (any?) challenge this morning.

I have 2 applications (A & B). Details of application A hopefully aren'y important, so the following is WRT AppB which has 3 projects, all class libraries:

  1. Exposes my Domain Entities to the rest of AppB, and is also exposed to AppA
  2. Exposes my Data Model to AppB only (uses L2S, normal context and entities)
  3. Exposes functionality to AppA, using the Domain Entities for parameters/return types

I have the following interfaces in the 3 different projects:

In Project 1 (Domain Entities)

VB:

Public Interface IAppB_Project1_Contract(Of TKey)
     ReadOnly Property UniqueReference As TKey
End Interface

C#

public interface IAppB_Project1_Contract<TKey>
{
    TKey UniqueReference { get; }
}

In Project 2 (Data Model)

VB:

Public Interface IAppB_Project2_Contract(Of TKey)
     Inherits IAppB_Project1_Contract(Of TKey)
End Interface

C#:

public interface IAppB_Project2_Contract<TKey> : IAppB_Project1_Contract<TKey>
{
}

In Project 3 (Functionality)

VB:

Public Interface IAppB_Project3_Contract
     Function Create(Of T As {Class, IAppB_Project1_Contract(Of TKey)}, TKey)(ByVal entity As T) As TKey
End Interface

C#:

public interface IAppB_Project3_Contract
{
    TKey Create<T, TKey>(T entity) where T : class, IAppB_Project1_Contract<TKey>;
}

The idea was that the functionality contracts (in Project 3) would only use Domain Entities (in Project 1) as arguments/returns, because only Projects 1 and 3 are exposed to Application A, Project 2 is not.

My L2S entities are extended to implement the Project 2 Interface. There are actually several additional methods on Project 2's contract but hopefully not relevant.

Until I actually typed it, for some reason I had it in my head that I would get away with the following implementation of Project 3's contract:

VB:

Public Class Project3_Implementation
    Implements IAppB_Project3_Contract

    Public Function Create(Of T As {Class, IApp_Project2_Contract(Of TKey)}, TKey)(ByVal entity As T) As TKey Implements IAppB_Project3_Contract.Create
        ' notice the very subtle change to *Project TWO's* contract in the generics' constraint
    End Function
End Class

C#:

public class Project3_Implementation : IAppB_Project3_Contract
{
    public TKey Create<T, TKey>(T entity) where T : class, IApp_Project2_Contract<TKey>
    {
        // notice the very subtle change to *Project TWO's* contract in the generics' constraint
    }
}

This worked in my head for a while because Project 2's contract inherits Project 1's, but this of course is not the case because I have narrowed the constraint which therefore cannot implement the corresponding project 3 interface.

The question is, how would I go about achieving what I sought:

I've got a feeling I'm asking the impossible, but I'm hoping someone outside the box looking in can suggest something I can change to achieve, or come close to achieving my goals?

Thanks in advance!!

EDIT:

I knew the question wouldn't be clear because what I've described in the question is simply how to do something wrong...

Here's more details on the implementation of Project 3's contract which hopefully makes things more clear (bearing in mind that Project 2's contract is only implemented by auto-generated Linq Entities):

VB:

Public Class MyImplementation
    Inherits System.Data.Linq.DataContext
    Implements IAppB_Project3_Contract

    Public Function Create(Of T As {Class, IApp_Project2_Contract(Of TKey)}, TKey)(ByVal entity As T) As TKey Implements IAppB_Project3_Contract.Create
        Me.GetTable(Of T).InsertOnSubmit(entity)
        Return entity.UniqueReference        ' the entity will implement the IApp_Project2_Contract by exposings it primary key - Return Me.MyPrimaryKeyField
    End Function
End Class

C#:

public class MyImplementation : System.Data.Linq.DataContext, IAppB_Project3_Contract
{
    public TKey Create<T, TKey>(T entity) where T : class, IApp_Project2_Contract<TKey>
    {
        this.GetTable<T>.InsertOnSubmit(entity);
        return entity.UniqueReference;
        // the entity will implement the IApp_Project2_Contract by exposings it primary key - Return Me.MyPrimaryKeyField
    }
}

Upvotes: 0

Views: 159

Answers (1)

Jodrell
Jodrell

Reputation: 35726

What happens if you declare you interfaces like this, this would require .Net 4.0+

Public Interface IAppB_Project1_Contract(Of Out TKey)
    ReadOnly Property UniqueReference As TKey 
End Interface

Public Interface IAppB_Project2_Contract(Of Out TKey)
    Inherits IAppB_Project1_Contract(Of TKey)
End Interface

Public Interface IAppB_Project3_Contract
    Function Create(Of T As {Class, IAppB_Project1_Contract(Of TKey)}, 
                    TKey)(ByVal entity As T) As TKey       
End Interface

Would this make the inheritors of the interfaces covariant with the parent types and enable the code to run?

Upvotes: 1

Related Questions