Yanick Rochon
Yanick Rochon

Reputation: 53521

InvalidCastException on Generics

Coming from the Java world, programming with generics and C# is often a headache. Like this one:

interface ISomeObject { }
class SomeObjectA : ISomeObject { }
class SomeObjectB : ISomeObject { }


interface ISomething<T> where T : ISomeObject
{
    T GetObject();
}
class SomethingA : ISomething<SomeObjectA>
{
    public SomeObjectA GetObject() { return new SomeObjectA(); }
}
class SomethingB : ISomething<SomeObjectB>
{
    public SomeObjectB GetObject() { return new SomeObjectB(); }
}


class SomeContainer
{

    private ISomething<ISomeObject> Something;

    public void SetSomething<T>(ISomething<T> s) where T : ISomeObject
    {
        Something = (ISomething<ISomeObject>)s;
    }
}


class TestContainerSomething
{
    static public void Test()
    {
        SomeContainer Container = new SomeContainer();
        Container.SetSomething<SomeObjectA>(new SomethingA());
    }
}

Which results into an InvalidCastException at Something = (ISomething<ISomeObject>)s;. In Java, this would work, and I could even use (if all else fails) the generics wildcard <?>. This is not possible in C#.

While this is just an example that I put together to explain the problematic, how can this exception be eliminated? The only main constraint is that SomeContainer cannot be a generic class

** Note ** : there are many questions about this, but none of them (that I could find) address a generic class member inside a non generic class.

** Update **

Inside the method SetSomething, I added these lines :

Console.WriteLine(s.GetType().IsSubclassOf(typeof(ISomething<SomeObjectA>)));
Console.WriteLine(s.GetType().ToString() + " : " + s.GetType().BaseType.ToString());
foreach (var i in s.GetType().GetInterfaces())
{
    Console.WriteLine(i.ToString());
}

which to my surprise output

False
SomeThingA : System.Object
ISomething`1[SomeObjectA]

Is this why I get this exception?

Upvotes: 5

Views: 1448

Answers (2)

Arsen Mkrtchyan
Arsen Mkrtchyan

Reputation: 50712

Out keyword will be a fix, if your ISomething only have methods that return T

interface ISomething<out T> where T : ISomeObject

when creating a generic interface, you can specify whether there is an implicit conversion between interface instances that have different type arguments.

It is called Covariance and Contravariance

Eric Lippert have a good series of articles why we need to think about this, here interface variance is used

Here is my code, which works as expected for me

interface ISomeObject { }
class SomeObjectA : ISomeObject { }
class SomeObjectB : ISomeObject { }


interface ISomething<out T> where T : ISomeObject
{
    T GetObject();
}
class SomethingA : ISomething<SomeObjectA>
{
    public SomeObjectA GetObject() { return new SomeObjectA(); }
}
class SomethingB : ISomething<SomeObjectB>
{
    public SomeObjectB GetObject() { return new SomeObjectB(); }
}


class SomeContainer
{

    private ISomething<ISomeObject> Something;

    public void SetSomething<T>(ISomething<T> s) where T : ISomeObject
    {
        Something = (ISomething<ISomeObject>)s;
    }
}


class TestContainerSomething
{
    static public void Test()
    {
        SomeContainer Container = new SomeContainer();
        Container.SetSomething<SomeObjectA>(new SomethingA());
    }
}

Upvotes: 5

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112259

Sometimes it is useful to let a generic interface implement a non generic one to circumvent the missing <?>

interface ISomething
{
    object GetObject();
}

interface ISomething<T> : ISomething
    where T : ISomeObject
{
    T GetObject();
}

public class SomeImplementation<T> : ISomething<T>
{
    public T GetObject()
    {
        ...
    }

    object ISomething.GetObject()
    {
        return this.GetObject(); // Calls non generic version
    }
}

A collection can then be typed with the non generic interface

var list = new List<ISomething>();
list.Add(new SomeImplementation<string>());
list.Add(new SomeImplementation<int>());

Upvotes: 2

Related Questions