0

I have the following code:

public class EntitySchema
{
}

public class ContactEntitySchema : EntitySchema
{
}

public class ProductEntitySchema : EntitySchema
{
}

public class Entity<TEntitySchema>
    where TEntitySchema : EntitySchema
{
    public string Id { get; set; }
}

public class Contact : Entity<ContactEntitySchema>
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Product : Entity<ProductEntitySchema>
{
    public string Name { get; set; }
}

public class ViewBase<TEntity>
    where TEntity : Entity<EntitySchema>
{
    public TEntity Entity { get; set; }
}

public class ContactView : ViewBase<Contact>
{
    public ContactView()
    {
    }
}

public class ProductView : ViewBase<Entity<EntitySchema>>
{
    public ProductView()
    {
    }
}

I get the following error for ContactView class:

Error 1 The type 'Generics.Contact' cannot be used as type parameter 'TEntity' in the generic type or method 'Generics.ViewBase<TEntity>'. There is no implicit reference conversion from 'Generics.Contact' to 'Generics.Entity<Generics.EntitySchema>'.

Then I define ProductView a bit different and it compiles successfully. However, in ProductView I should explicitly cast the Entity property to Product if I want to use some of the Product specific fields. Is there a way to rework the declaration of ContactView so I can specify the actual Entity type (in this particular case - Contact)?

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
Ivan
  • 3
  • 1
  • 1
    I think this is a problem of covariance/contravariance, take a look at [this thread](http://stackoverflow.com/questions/2662369/covariance-and-contravariance-real-world-example) – Binkan Salaryman Jun 03 '15 at 15:24

1 Answers1

1

The problem you're having is that public class Entity<TEntitySchema> is generically invariant. That means that even though you know that ContactEntitySchema : EntitySchema that doesn't mean the compiler can substitute ContactEntitySchema for EntitySchema in the generic type definition for ViewBase.

(To understand why that's the case, consider what happens if you try to treat a List<Cat> as if it were a List<Animal>, or vice-versa.)

I might have misunderstood your problem, but to start with you could try specifying the schema type directly in the type parameters for your view, using a type constraint to make sure the relationship between TEntity and TSchema is preserved?:-

public class ViewBase<TEntity, TSchema>
    where TEntity : Entity<TSchema>
    where TSchema : EntitySchema
{
    public TEntity Entity { get; set; }
}

public class ContactView : ViewBase<Contact, ContactEntitySchema>
{
    public ContactView()
    {
    }
}

public class ProductView : ViewBase<Entity<EntitySchema>, EntitySchema>
{
    public ProductView()
    {
    }
}

Alternately, you can change Entity into an interface:-

public interface IEntity<out TEntitySchema>
    where TEntitySchema : EntitySchema
{
    string Id { get; set; }
}

public class Contact : IEntity<ContactEntitySchema>
{
    public string Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
// etc

which allows you to specify that the TEntitySchema generic type parameter is covariant (using the out keyword).

Either solution should allow you to access this.TEntity in a strongly-typed fashion in your view objects.

Iain Galloway
  • 18,669
  • 6
  • 52
  • 73
  • Thank you very much! I reworked the code as you suggested and now everything works fine! :) – Ivan Jun 04 '15 at 06:02