Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a generic type parameter as type parameter for a property declared as an interface with type constraints?

Tags:

c#

generics

How do I declare and use generic interfaces (see namespace Sample2) to work in the same way as with classes in namespace Sample1?

I know there is a workaround (see namespace Sample2Modified) but that is not what I'm trying to achieve.


Works with classes

When using classes, this works fine:

namespace Sample1
{
    public class ClassContainer<T1, T2>
        where T1 : T2
    { }

    public class ClassParent
    { }

    public class ClassChild : ClassParent
    { }

    public class ClassSampleDoesWork<TItem>
        where TItem : ClassParent
    {
        ClassContainer<IEnumerable<TItem>, IEnumerable<ClassParent>> SomeProperty { get; set; }
    }
}

Does not work with interfaces

However, with interfaces the compiler gives this warning when declaring the property in ClassSampleDoesNotWork:

Error 16 The type 'System.Collections.Generic.IEnumerable<TItem>'
cannot be used as type parameter 'T1' in the generic type or method
'Sample2.IInterfaceContainer<T1,T2>'. There is no implicit reference
conversion from 'System.Collections.Generic.IEnumerable<TItem>' to
'System.Collections.Generic.IEnumerable<Sample2.IInterfaceParent>'.

Code:

namespace Sample2
{
    public interface IInterfaceContainer<T1, T2>
        where T1 : T2
    { }

    public interface IInterfaceParent
    { }

    public interface IInterfaceChild : IInterfaceParent
    { }

    public class ClassSampleDoesNotWork<TItem>
        where TItem : IInterfaceParent
    {
        IInterfaceContainer<IEnumerable<TItem>, IEnumerable<IInterfaceParent>> SomeProperty { get; set; }
    }
}

Modified version works, but this is not really what I want

If I modify the class to have TEnumerableOfItem instead of TItem, it works:

namespace Sample2Modified
{
    public interface IInterfaceContainer<T1, T2>
        where T1 : T2
    { }

    public interface IInterfaceParent
    { }

    public interface IInterfaceChild : IInterfaceParent
    { }

    public class ClassSampleModifiedDoesWork<TEnumerableOfItem>
        where TEnumerableOfItem : IEnumerable<IInterfaceParent>
    {
        IInterfaceContainer<TEnumerableOfItem, IEnumerable<IInterfaceParent>> SomeProperty { get; set; }
    }
}
like image 218
lightbricko Avatar asked Jul 08 '13 14:07

lightbricko


1 Answers

Try to add class constraint to the TItem:

namespace Sample2
{
    public interface IInterfaceContainer<T1, T2>            
        where T1 : T2
    { }

    public interface IInterfaceParent
    { }

    public interface IInterfaceChild : IInterfaceParent
    { }

    public class ClassSampleDoesNotWork<TItem>
        where TItem : class, IInterfaceParent
    {
        IInterfaceContainer<IEnumerable<TItem>, IEnumerable<IInterfaceParent>> SomeProperty { get; set; }
    }
}

This works because variance only works for reference-types (or there is an identity conversion). It isn't known that TItem is reference type, unless you add : class. Read this article for more information. Here is a sample code to demonstrate this behavior:

IEnumerable<Object> items = new List<int>(); // Compiler error.
like image 178
Vyacheslav Volkov Avatar answered Sep 29 '22 19:09

Vyacheslav Volkov