'How to avoid propagation of type constraints?

In the underlying use case I'm dealing with different serialization formats and depending on the configured format (via DI) a certain factory is used. However, I think I run into a more general problem here, therefore I simplified the following use case.

Suppose I have a simple interface with a generic method:

public interface IProducerFactory
{
    public IProducer<T> CreateProducer<T>();
}

Now I have a concrete implementation ProducerFactoryA, which works just fine. Even though BuilderA comes from an external library, I can easily create an instantiation implementing IProducer<T>:

public class ProducerFactoryA : IProducerFactory
{
    public IProducer<T> CreateProducer<T>()
    {
        // some configuration ...
        return new BuilderA<T>().Build();
    }
}

For ProducerFactoryB, I need to use the external library BuilderB. However, BuilderB has type constraints, which would require me to add them to the interface IProducerFactory and basically everywhere where I want to use it:

public class ProducerFactoryB : IProducerFactory
{
    public IProducer<T> CreateProducer<T>()
        where T : ISomeConstraint, new()
    {
        // some configuration ...
        return new BuilderB<T>().Build();
    }
}

My question is, how can I avoid such a situation? I clearly don't want ISomeConstraint which comes from an external library to be part of my interface and propagate this type throughout my codebase. Furthermore, it would also break ProducerFactoryA.

Is there a way to check these constraints to satisfy the compiler without adding them on the method/interface level?



Solution 1:[1]

The IProducerFactory makes a promise that any implementation can produce any kind of object, without restrictions. So if you want restrictions you would need to propagate them or get rid of them.

An alternative would be to declare the generic type as part of the interface:

public interface IProducerFactory<T>
{
    public IProducer<T> CreateProducer();
}

public class ProducerFactoryB : IProducerFactory<ISomeConstraint>
{
    public IProducer<ISomeConstraint> CreateProducer()
    {
        ...
    }
}

This makes a much weaker promise, i.e. if you somehow get a producer factory for a specific type, it can create producers of that type. You could also mix the patterns, and have one interface that can create producers for any type, and one that can only create specific types.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 JonasH