locked
Type Reference Conversion Issue with Interface and Generics RRS feed

  • Question

  • I'm in the process of developing my domain model and was trying to abstract out the core components.  I want to do this to allow to greatly simplify expansion.  If only abstracting out the core was as easy as that.  I have an ITransaction interface that is managed by the ITransactionManager (at least once implemented).  This TransactionManager can be reached via the DatabaseRegistry which implmenents it's own interface.  The idea here is supposed to be to abstract the interfaces and base classes to a core dll so that multiple versions of these can be created and function consistently.  But I'm getting the error:

    Error 1 The type 'Generics.WithInterfaceTypes.TransactionManager' cannot be used as type parameter 'TTransactionMgr' in the generic type or method 'Generics.WithInterfaceTypes.IDatabaseRegistry<TTransactionMgr>'. There is no implicit reference conversion from 'Generics.WithInterfaceTypes.TransactionManager' to 'Generics.WithInterfaceTypes.ITransactionManager<Generics.WithInterfaceTypes.ITransaction>'. C:\Projects - Visual Studio 2010\_.Samples.General\CSharpProgrammingCodeSample\Generics\WithInterfaceTypes\DatabaseRegistry.cs 8 18 Generics (Generics\Generics)

    This error occurs in the DatabaseRegistry class.

      This is what I have:

        public interface ITransaction
        {
            IEntity Get();
            IEntityCollection GetCollection();
        }


        public interface ITransactionManager<TTransaction>
            where TTransaction : ITransaction
        {
            List<TTransaction> Transactions { get; set; }
            TTransaction Start();
        }

    public class TransactionManager : ITransactionManager<Transaction> { #region ITransactionManager<Transaction> Members private List<Transaction> _transactions = new List<Transaction>(); public List<Transaction> Transactions { get { return _transactions; } set { _transactions = value; } } public Transaction Start() { Transaction trans = new Transaction(); this.Transactions.Add(trans); return trans; } #endregion }


        public interface IDatabaseRegistry<TTransactionMgr>
            where TTransactionMgr:ITransactionManager<ITransaction>
        {
            TTransactionMgr TransactionManager { get; }
        }

        public class DatabaseRegistry:IDatabaseRegistry<TransactionManager>
        {
            #region IDatabaseRegistry Members
            private TransactionManager _transactionManager = new TransactionManager();
            public TransactionManager TransactionManager
            {
                get { return _transactionManager; }
            }
            #endregion
        }

    So that the client code could look like:

        TransactionManager trnMgr = new TransactionManager();
                Transaction trans = trnMgr.Start();

    Any help would be much, much, much appreciated.


    Jason

    • Moved by CoolDadTxModerator Friday, October 19, 2012 10:15 PM Language related (From:Visual C# General)
    Friday, October 19, 2012 9:32 PM

Answers

  • In a nutshell covariance on a generic interface means that a more derived generic type can be replaced with a less generic type and still be valid.  For example IEnumerable<T> is covariant.  Therefore if you have a IEnumerable<Derived> then you can treat it as an IEnumerable<Base>.  Prior to 4 this wouldn't work because, while IEnumerable<Derived> and IEnumerable<Base> look like they form a hierarchy, they don't.  Both derive from IEnumerable<T> which derives from non-generic interfaces.  Only the type parameters form a hierarchy but that doesn't matter.  The requirement for a covariant generic interface is that the interface can only use the type as a return value or as a type parameter to another covariant interface.  There might be a few more cases I'm forgetting.  Covariance effectively gives you the inheritance behavior that you might normally assume would work with generics.

    Contravariance is far less common but can still be useful.  Contravariance sort of works backwards.  It allows you to pass a less derived type parameter to something that requires a more derived type.  The key for this type of variance is that the type parameter can only be used as input to methods.  IComparer<T> is an example of a contravariant interface.  I recommend that you read up on this subject in MSDN as it can be quite confusing.

    In summary, if your interface only returns the type parameter then it can be covariant.  If your interface only uses the type parameter as input parameters to methods it may be contravariant.  If your interface does both then it can't be either.

    Your ITransactionManager<T> interface only returns the type parameter but it uses List<T> which is not covariant because of the Add method.  Therefore ITransactionManager can be neither.

    Your ITransaction interface is not generic at all but if it were it could be covariant if IEntityCollection were instead IEnumerable<IEntity>.

    IDatabaseRegistry<T> only returns the type parameter so it could be covariant.

    I think you'll need to reconsider your interfaces before you can get what you want.  As they stand now they just won't work.  I think you're trying to combine the base types with the interfaces a little too much.  In general you'll create an interface and its corresponding base impl.  The more specific types are separate.

    Here's a first pass at what you could do.  Note that ITransaction seems like it should be generic and accept IEntity.  IEntityCollection seems like could go away and have ITransaction either derive from IEnumerable<T> or ICollection<T>.

    public interface IEntity { };
    public interface IEntityCollection { };
    
    public interface ITransaction 
    {
        IEntity Get ();
        IEntityCollection GetCollection ();
    }
    
    public class Transaction : ITransaction
    {
        public IEntity Get () { return null; }
        public IEntityCollection GetCollection () { return null; }
    }
    
    public interface ITransactionManager<out T> where T: ITransaction
    {
        IEnumerable<T> Transactions { get; }
        T Start ();
    }
        
    public class TransactionManager<T> : ITransactionManager<T> where T : ITransaction, new()
    {
        public IEnumerable<T> Transactions 
        {
            get { return _transactions; }
        }
    
        public T Start ()
        {
            var trans = new T();
            _transactions.Add(trans);
    
            return trans;
         }
    
         private List<T> _transactions = new List<T>();
    }
    
    public interface IDatabaseRegistry<out T> where T : ITransactionManager<ITransaction>
    {
        T TransactionManager { get; }
    }
    
    public class DatabaseRegistry<T> : IDatabaseRegistry<T> where T: ITransactionManager<ITransaction>, new()
    {
        private T _transactionManager = new T();
    
        public T TransactionManager { get { return _transactionManager; } }
    }
    
    public class TransactionDatabaseRegistry : DatabaseRegistry<TransactionManager<Transaction>>
    {
    }
    
    class Program
    {
        static void Main ( string[] args )
        {
            var db = new TransactionDatabaseRegistry();
            var trans = db.TransactionManager.Start();
        }
    }

    You can greatly simplify this if you move away from combining interfaces and implementation types altogether and just use interfaces in all your definitions with impl types only as convenience functions.  Then in your main program you instantiate the impl type.  But the above code should be a good starter.  I don't even think the TransactionDatabaseRegistry type is needed but I included it to keep the "separate interface from impl" concept going.

    Michael Taylor - 10/20/2012
    http://msmvps.com/blogs/p3net

    • Marked as answer by HuffCAD Wednesday, October 24, 2012 11:13 PM
    Saturday, October 20, 2012 5:06 PM
    Moderator
  • What I meant was that interfaces should only consume and use primitives, value types (not a C# struct but a type that's sole purpose is provide a collection of other values) or other interfaces.  An interface is a contract, not an implementation, so the types it uses should follow the same rules.  You can pass business objects and whatnot to an interface but you shouldn't pass interface implementations to an interface as you're then dictating implementation.  As an example most people use List<T> or Collection<T> to pass collections of items around but an interface shouldn't restrict a caller to that so an interface should always use IEnumerable<T> or ICollection<T> (or similar). 

    As part of an interface design you will generally define a corresponding abstract base class that implements that interface and handles the core details.  The base implementation should not put any restrictions on the derived implementations so if the interface is generic then so should the abstract base class.  From the base class you can then create more concrete derived classes as needed.  In your sample code you defined ITransactionManager<T> and TransactionManager which implemented ITransactionManager<Transaction>.  Thus you defined a base impl of the interface but your base impl mandated that only Transaction be used rather than the more general ITransaction that the interface defined.  You were putting an undo restriction on derived types.  TransactionManager should be defined generic as well.  Then you could create a concrete impl of TransactionManager that used Transaction if you so chose but unless you wanted to hide generics altogether you wouldn't have actually gained much by doing so. 

    Michael Taylor - 10/24/2012
    http://msmvps.com/blogs/p3net

    • Marked as answer by HuffCAD Wednesday, October 24, 2012 11:13 PM
    Wednesday, October 24, 2012 9:57 PM
    Moderator

All replies

  • It is a common misconception that generics work just like standard inheritance.  For example assume Transaction derives from ITransaction.  Then it is expected that ICollection<Transaction> can be used for ICollection<ITransaction> but that is not true.  They are 2 completely different types.  One accepts Transaction and the other accepts ITransaction.  If it were true then you could write code like this:

    void AddBadItem ( ICollection<ITransaction> items )
    {
       items.Add(new SomeTypeThatImplementsITransaction());
    }

    ICollection<Transaction> someList;
    AddBadItem(someList);

    Now ICollection<Transaction> has an element that isn't a Transaction.  This is formally known as covariance/contravariance in language parlance. 

    Fortunately C# added support for these in 4 so you can work around it but you have to identify which type parameters are covariant and which are contravariant.  You then add the appopriate keyword to the generic definition and suddenly it'll work.  But each has restrictions on what operations are then allowed.  Refer to the following MSDN article about how this works and then attribute your generic definition based upon your needs: http://msdn.microsoft.com/en-us/library/dd799517.aspx

    Michael Taylor - 10/19/2012
    http://msmvps.com/blogs/p3net

    Friday, October 19, 2012 10:15 PM
    Moderator
  • Well, I have to admit it gets pretty confusing when I can implicitly convert types anywhere else but generics.  I also tried looking at the variant approached and still couldn't seem to get it to work.  I had to leave the office finally this afternoon, but should be back in tomorrow for a few minutes.  I may try to take a fresh look at that again.  If you have any direct suggestions, I'm definitely all ears.  I feel like I've spent way too long on something that seems like it should be very simple.

    Jason

    Friday, October 19, 2012 10:41 PM
  • It's the nested types that it doesn't seem to like... so if I reduce:

        public interface ITransactionManager<TTransaction>
            where TTransaction : ITransaction
        {
            List<TTransaction> Transactions { get; set; }
            TTransaction Start();
        }
        public interface ITransactionManager
            //where TTransaction : ITransaction
        {
            //List<TTransaction> Transactions { get; set; }
            //TTransaction Start();
        }

    Then adjust everything else accordingly, this seems to compile without a problem.  That tells me it doesn't seem to like the nested type interface declaration in IDatabaseRegistry:

    public interface IDatabaseRegistry<TTransactionMgr> where TTransactionMgr:ITransactionManager<ITransaction> { TTransactionMgr TransactionManager { get; } }


    Any suggestions for handling this?


    Jason

    Saturday, October 20, 2012 3:49 PM
  • ahhh heck... It just struck me that if it is because of the IDatabaseRegistry, then it may not like the fact that the type TTransactionManager is defined using an interface ITransaction, that maybe both need to be defined as types.  So I changed the interface declaration to be:

        public interface IDatabaseRegistry<TTransMgr, TTrans>
            where TTransMgr:ITransactionManager<TTrans>
            where TTrans:ITransaction
        {
            TTransMgr TransactionManager { get; }
        }

    and aula, it works... of course this is in a test project where I was trying to keep figuring this think out in a simple environment.  I'll apply this thought to my actual project and let you know the results.  It may take me a bit as I've got to do some other things with it too before I can verify.


    Jason

    Saturday, October 20, 2012 3:57 PM
  • In a nutshell covariance on a generic interface means that a more derived generic type can be replaced with a less generic type and still be valid.  For example IEnumerable<T> is covariant.  Therefore if you have a IEnumerable<Derived> then you can treat it as an IEnumerable<Base>.  Prior to 4 this wouldn't work because, while IEnumerable<Derived> and IEnumerable<Base> look like they form a hierarchy, they don't.  Both derive from IEnumerable<T> which derives from non-generic interfaces.  Only the type parameters form a hierarchy but that doesn't matter.  The requirement for a covariant generic interface is that the interface can only use the type as a return value or as a type parameter to another covariant interface.  There might be a few more cases I'm forgetting.  Covariance effectively gives you the inheritance behavior that you might normally assume would work with generics.

    Contravariance is far less common but can still be useful.  Contravariance sort of works backwards.  It allows you to pass a less derived type parameter to something that requires a more derived type.  The key for this type of variance is that the type parameter can only be used as input to methods.  IComparer<T> is an example of a contravariant interface.  I recommend that you read up on this subject in MSDN as it can be quite confusing.

    In summary, if your interface only returns the type parameter then it can be covariant.  If your interface only uses the type parameter as input parameters to methods it may be contravariant.  If your interface does both then it can't be either.

    Your ITransactionManager<T> interface only returns the type parameter but it uses List<T> which is not covariant because of the Add method.  Therefore ITransactionManager can be neither.

    Your ITransaction interface is not generic at all but if it were it could be covariant if IEntityCollection were instead IEnumerable<IEntity>.

    IDatabaseRegistry<T> only returns the type parameter so it could be covariant.

    I think you'll need to reconsider your interfaces before you can get what you want.  As they stand now they just won't work.  I think you're trying to combine the base types with the interfaces a little too much.  In general you'll create an interface and its corresponding base impl.  The more specific types are separate.

    Here's a first pass at what you could do.  Note that ITransaction seems like it should be generic and accept IEntity.  IEntityCollection seems like could go away and have ITransaction either derive from IEnumerable<T> or ICollection<T>.

    public interface IEntity { };
    public interface IEntityCollection { };
    
    public interface ITransaction 
    {
        IEntity Get ();
        IEntityCollection GetCollection ();
    }
    
    public class Transaction : ITransaction
    {
        public IEntity Get () { return null; }
        public IEntityCollection GetCollection () { return null; }
    }
    
    public interface ITransactionManager<out T> where T: ITransaction
    {
        IEnumerable<T> Transactions { get; }
        T Start ();
    }
        
    public class TransactionManager<T> : ITransactionManager<T> where T : ITransaction, new()
    {
        public IEnumerable<T> Transactions 
        {
            get { return _transactions; }
        }
    
        public T Start ()
        {
            var trans = new T();
            _transactions.Add(trans);
    
            return trans;
         }
    
         private List<T> _transactions = new List<T>();
    }
    
    public interface IDatabaseRegistry<out T> where T : ITransactionManager<ITransaction>
    {
        T TransactionManager { get; }
    }
    
    public class DatabaseRegistry<T> : IDatabaseRegistry<T> where T: ITransactionManager<ITransaction>, new()
    {
        private T _transactionManager = new T();
    
        public T TransactionManager { get { return _transactionManager; } }
    }
    
    public class TransactionDatabaseRegistry : DatabaseRegistry<TransactionManager<Transaction>>
    {
    }
    
    class Program
    {
        static void Main ( string[] args )
        {
            var db = new TransactionDatabaseRegistry();
            var trans = db.TransactionManager.Start();
        }
    }

    You can greatly simplify this if you move away from combining interfaces and implementation types altogether and just use interfaces in all your definitions with impl types only as convenience functions.  Then in your main program you instantiate the impl type.  But the above code should be a good starter.  I don't even think the TransactionDatabaseRegistry type is needed but I included it to keep the "separate interface from impl" concept going.

    Michael Taylor - 10/20/2012
    http://msmvps.com/blogs/p3net

    • Marked as answer by HuffCAD Wednesday, October 24, 2012 11:13 PM
    Saturday, October 20, 2012 5:06 PM
    Moderator
  • I see what you've done and it works wonderfully.  I have made some minor modifications... mostly type names, adding method parameters, etc.  Do you mind expanding on what you mean by:

    "You can greatly simplify this if you move away from combining interfaces and implementation types altogether and just use interfaces in all your definitions with impl types only as convenience functions.  Then in your main program you instantiate the impl type."


    Jason

    Wednesday, October 24, 2012 8:53 PM
  • What I meant was that interfaces should only consume and use primitives, value types (not a C# struct but a type that's sole purpose is provide a collection of other values) or other interfaces.  An interface is a contract, not an implementation, so the types it uses should follow the same rules.  You can pass business objects and whatnot to an interface but you shouldn't pass interface implementations to an interface as you're then dictating implementation.  As an example most people use List<T> or Collection<T> to pass collections of items around but an interface shouldn't restrict a caller to that so an interface should always use IEnumerable<T> or ICollection<T> (or similar). 

    As part of an interface design you will generally define a corresponding abstract base class that implements that interface and handles the core details.  The base implementation should not put any restrictions on the derived implementations so if the interface is generic then so should the abstract base class.  From the base class you can then create more concrete derived classes as needed.  In your sample code you defined ITransactionManager<T> and TransactionManager which implemented ITransactionManager<Transaction>.  Thus you defined a base impl of the interface but your base impl mandated that only Transaction be used rather than the more general ITransaction that the interface defined.  You were putting an undo restriction on derived types.  TransactionManager should be defined generic as well.  Then you could create a concrete impl of TransactionManager that used Transaction if you so chose but unless you wanted to hide generics altogether you wouldn't have actually gained much by doing so. 

    Michael Taylor - 10/24/2012
    http://msmvps.com/blogs/p3net

    • Marked as answer by HuffCAD Wednesday, October 24, 2012 11:13 PM
    Wednesday, October 24, 2012 9:57 PM
    Moderator
  • Okay, that makes sense.  I really appreciate your time and patience.  Thank you


    Jason

    Wednesday, October 24, 2012 11:12 PM