Search code examples
c#genericsinterfacetype-constraints

Partial generic interface with type constraint


I need to create two partial interfaces. One with a constraint and the other without, like:

public partial interface IMyCuteInterface<T> where T : IEnumerable
{
    void DoSomethingOnlyPossibleIfGenericIsIEnumerable();
}

public partial interface IMyCuteInterface<T>
{
    void DoSomeStuff();
    void DoSomeStuff2();
}

This is the implementation:

public class CuteInterfaceImplementation<T> : IMyCuteInterface<T>
{
    private readonly T _element;
    public CuteInterfaceImplementation(T element)
    {
        _element = element;
    }

    public void DoSomethingOnlyPossibleIfGenericIsIEnumerable(){}
    public void DoSomeStuff(){}
    public void DoSomeStuff2() { }
}

This is a static method to get this more dynamically:

public class CuteInterfaceImplementationBase
{
    public static IMyCuteInterface<T> From<T>(T t)
    {
        return new CuteInterfaceImplementation<T>(t);
    }
}

and this is the way I want to call it:

public static void Main(string[] args)
{
    var mci = CuteInterfaceImplementationBase.From(args);
}

So, C# wants me to add the generic type constraint I added in the first interface to my CuteInterfaceImplementationBase.From<T> and my CuteInterfaceImplementation<T>-class.

What I want to achieve is: args could either be e.g. from type List<T> or from type int or something else. My target is, if args is from type IEnumerable<T> I want to add more functions (via the interface with the constraint) to CuteInterfaceImplementation-instance.

example:

if args is from type IEnumerable, this instance from CuteInterfaceImplementation has methods:

  • void DoSomethingOnlyPossibleIfGenericIsIEnumerable();
  • void DoSomeStuff();
  • void DoSomeStuff2();

if args is from type Foo or int (or any type that doesn't implement IEnumerable) I can use methods:

  • void DoSomeStuff();
  • void DoSomeStuff2();

means, DoSomethingOnlyPossibleIfGenericIsIEnumerable is not available.

But it seems, this is not possible, since I need to add the constraint to my implemented class. Any idea how to do this?


Solution

  • Not sure that this approach good idea, it violates the "I" in SOLID - interface segregation

    no client should be forced to depend on methods it does not use

    You're using partial to split up two fundamentally different interfaces, you should have 2 different interface because they are different.

    To answer your question: If you're committed to a similar approach on the conditions of T, you could split the interfaces, move the "common logic" (which both interfaces use) to a base class and use the From<T> method to conditionally choose which implementation to create.

    Something like this:

    public partial interface IMyCuteInterface_WITHEnumerable<T> : IMyCuteInterface<T> where T : IEnumerable
    {
        void DoSomethingOnlyPossibleIfGenericIsIEnumerable();
    }
    
    public partial interface IMyCuteInterface<T>
    {
        void DoSomeStuff();
        void DoSomeStuff2();
    }
    

    And then the implementations:

    public class CuteInterfaceImplementation<T> : CuteInterfaceImplementation_COMMON<T>
    {
        public CuteInterfaceImplementation(T element) : base(element)
        {
    
        }
    }
    
    public class CuteInterfaceImplementation_COMMON<T> : IMyCuteInterface<T>
    {
        private readonly T _element;
        public CuteInterfaceImplementation_COMMON(T element)
        {
            _element = element;
        }
        public void DoSomeStuff() { }
        public void DoSomeStuff2() { }
    }
    
    public class CuteInterfaceImplementation_WITHEnumerable<T> : CuteInterfaceImplementation_COMMON<T>,  IMyCuteInterface_WITHEnumerable<T> where T : IEnumerable
    {
        private readonly T _element;
        public CuteInterfaceImplementation_WITHEnumerable(T element) : base(element)
        {
            _element = element;
        }
    
        public void DoSomethingOnlyPossibleIfGenericIsIEnumerable() { }
    }
    

    Finally your "static helper", which decides on the class to instantiate: Unfortunately it's not possible in C# to conditionally instantiate the different classes because one expects T to be IEnumerable while the other doesn't. You can get around that using dynamic

    public class CuteInterfaceImplementation_HELPER
    {
        public static IMyCuteInterface<T> From<T>(T t)
        {
            if (t is IEnumerable)
            {
                dynamic dyn = t;
                return FromEnumerable(dyn);
            }
            else
            {
                return new CuteInterfaceImplementation<T>(t);
            }
        }
    
        public static IMyCuteInterface<T> FromEnumerable<T>(T t) where T: IEnumerable
        {
            return new CuteInterfaceImplementation_WITHEnumerable<T>(t);
        }
    }