tags:

views:

41

answers:

1

I have problem with constraints on generic method. Here is code for all classes:

namespace Sdk.BusinessObjects
{
    public interface IBusinessObject
    {
    }
}

namespace Sdk.BusinessObjects
{
    [DataContract]
    public class AccountDetails : IBusinessObject
    {
        [DataMember]
        public virtual Guid AccountId { get; set; }

    // More properties...
    }
}

namespace Sdk.BusinessLogic
{
    public interface IManager<T> where T : IBusinessObject
    {
        T Add(T businessObject);
        void Delete(T businessObject);
        IList<T> ListAll();
    }
}

namespace Sdk.BusinessLogic
{
    public interface IAccountManager : IManager<AccountDetails>
    {
        void ChangeAccountState(Guid accountId, string state);
    }
}

namespace Sdk.BusinessLogic
{
    public interface IManagerFactory
    {
        T Create<T>() where T : IManager<IBusinessObject>;
    }

    public class ManagerFactory : IManagerFactory
    {
        public T Create<T>() where T : IManager<IBusinessObject>
        {
            // resolve with Unity and return
        }
    }
}

So, I have main IBusinessObject interface for all business objects (like AccountDetails) and IManager as generic manager interface for business objects. I wanted to create factory for these managers with constraints. When I try something like this in UnitTest:

IManagerFactory factory = new ManagerFactory();
factory.Create<IAccountManager>();

I get error: The type 'Sdk.BusinessLogic.IAccountManager' cannot be used as type parameter 'T' in the generic type or method 'Sdk.BusinessLogic.IManagerFactory.Create()'. There is no implicit reference conversion from 'Sdk.BusinessLogic.IAccountManager' to 'Sdk.BusinessLogic.IManager'.

How can this be done?

+1  A: 

Basically your problem is that IManager<T> is invariant, and has to be as you've got values coming out of the API and values going into it. So an IAccountManager isn't an IManager<IBusinessObject>, because otherwise you could write:

IAccountManager m1 = new SomeImplementation();
IManager<IBusinessObject> m2 = m1;
m2.Add(new SomeArbitraryBusinessObject());

An account manager is only meant to manage accounts, not just any business object.

One option is to use two generic type parameters instead of one for ManagerFactory.Create:

public TManager Create<TManager,TObjectType>()
    where TManager : IManager<TObjectType>
    where TObjectType : IBusinessObject
Jon Skeet
Yes, you are right, but I don't like implementation with two generic type parameters because there is no sense to try to resolve IAccountManager with some other implementation of IBusinessObject. I think that best solution is to drop constraint for generic type parameter and to enforce it in the implementation: T Create<T>();
IvanQ
@IvanQ: It depends on whether you want to have compile-time safety, basically. Another alternative is to create a non-generic `IManager` interface which `IManager<T>` derives from.
Jon Skeet
Yes, I was thinking about non-generic IManager, but it would be empty so I rejected that idea for now.
IvanQ