3

Updated with input from MarcinJuraszek

I have a feeling I'm bumping into a co / contra variance problem here, but I'm not sure I understand how to fix it. I have a class like this:

public interface ISomeClass<TEnum, out S>
{
     TEnum Dim { get; }
     IEnumerable<S> Inc { get; }
}

public class SomeClass<TEnum, S> : ISomeClass<TEnum, S>
    where TEnum : struct, IConvertible
    where S : IMyInterface
{
    public TEnum Dim { get; set; }
    public IEnumerable<S> Inc { get; set; }
}

and I have a class that implements IMyInterface

public class MyImplementation : IMyInterface
{

}

And, of course, I have a class with a SomeClass property:

public class MyContainer<TEnum> where TEnum : struct, IConvertible
{
    public SomeClass<TEnum, IMyInterface> MyProp { get; set; }
}

Now my problem is that I can't assign a SomeClass<MyEnum, MyImplementation> to the MyProp property because I get an InvalidCastException at runtime complaining that it can't cast the SomeClass<MyEnum, MyImplementation> to SomeClass<MyEnum, IMyInterface>.

How do I work around this?

Example, this doesn't compile:

var c = new MyContainer<MyEnum>();
c.MyProp = new SomeClass<MyEnum, MyImplementation>();

Here's a dot net fiddle

Matt Burland
  • 44,552
  • 18
  • 99
  • 171

1 Answers1

4

You can make it work by having your generic type parameter invariant (either covariant or contravariant, depending on its members). However, in C# you can only declare generic parameters invariant on interface, so you'd have to declare another interface:

public interface ISomeClass<TEnum, in S>
{

}

public class SomeClass<TEnum, S> : ISomeClass<TEnum, IMyInterface>
    where TEnum : struct, IConvertible
    where S : IMyInterface
{

}

public class MyContainer<TEnum> where TEnum : struct, IConvertible
{
    public ISomeClass<TEnum, IMyInterface> MyProp { get; set; }
}

That would make following code compile:

var container = new MyContainer<DayOfWeek>();
container.MyProp = new SomeClass<DayOfWeek, MyImplementation>();

Another possible solution would be to use another interface, where S generic type parameter doesn't exist:

public interface ISomeClass<TEnum>
    where TEnum: struct, IConvertible
{

}

public class SomeClass<TEnum, S> : ISomeClass<TEnum>
    where TEnum : struct, IConvertible
    where S : IMyInterface
{

}

public class MyContainer<TEnum> where TEnum : struct, IConvertible
{
    public ISomeClass<TEnum> MyProp { get; set; }
}

Bonus - as of why it doesn't work:

Let's imagine that your code compiles, and you can assign MyClass<T> to MyClass<IT> as long as T implements IT. You could have following class:

class MyClass<T>
{
    public List<T> MyProp { get; set; }
}

And do

MyClass<IMyInterface> instance = new MyClass<MyInterfaceImplementation>();

with that instance.MyProp would be List<MyInterfaceImplementation> but you had access to it as if it was List<IMyInterface> so you could try adding element of MyOtherInterfaceImplementation which would crash at runtime. Not fun.

MarcinJuraszek
  • 124,003
  • 15
  • 196
  • 263
  • 1
    Do you know why only interfaces and delegates can have variant type parameter? – Arthur Rizzo Jan 12 '15 at 21:34
  • 1
    I don't have answer to that question. – MarcinJuraszek Jan 12 '15 at 21:37
  • if anyone has interest in the whys: http://stackoverflow.com/questions/2733346/why-isnt-there-generic-variance-for-classes-in-c-sharp-4-0 – Arthur Rizzo Jan 12 '15 at 21:40
  • Thanks for the answer, but this runs me into the problem that I can't add any properties to the interface. If I add a property then the compiler complains that "the type parameter 'S' must be invariantly valid on..." – Matt Burland Jan 12 '15 at 22:13
  • Change variance type using `out` instead of `in`. It will allow you to add get-only properties as well as methods that return `S`, but you won't be able to have set-properties and methods that take parameter of type `S`. – MarcinJuraszek Jan 12 '15 at 22:14
  • Thanks again, your input is really helping but I'm still not getting it working. I've updated the question to reflect my real situation a little more and include a fiddle. My `SomeClass` actually includes an `IEnumerable` and maybe that's what's tripping me up here. – Matt Burland Jan 13 '15 at 14:44