5

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?

Yanick Rochon
  • 51,409
  • 25
  • 133
  • 214

2 Answers2

5

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());
    }
}
Arsen Mkrtchyan
  • 49,896
  • 32
  • 148
  • 184
  • I'm trying to use `out` but it gives me some error (no line) about not being able to load the `ISomething_IMpl'1'` from the assembly :)` – Yanick Rochon Nov 29 '12 at 16:11
  • which version of .net are you using? as I remember it become available in .net 3+, I am on .net 4 and it works – Arsen Mkrtchyan Nov 29 '12 at 16:18
  • The latest, I had ".Net 4 Client Profile" and changed to ".Net 4" but same result. For the record, I just changed `interface ISomething where T : ISomeObject` for `interface ISomething`. – Yanick Rochon Nov 29 '12 at 16:33
  • Yes, I juest tried it and it says that I cannot load the interface because it declares an argument of type covariant which is neither an interface or a delegate. Funny, isn't it? BTW : I have all the updates installed, .Net 4, VS2010 Ultimate (legit) and all. – Yanick Rochon Nov 29 '12 at 17:23
  • @YanickRochon, are you trying the same interface as here? could you please copy exact error message please, I am not sure why you can get that messag? are you trying this in one project? – Arsen Mkrtchyan Nov 29 '12 at 18:35
  • The error message is in french, I tried to translate it :) And, yes, I'm using the exact same interface as here, except it's in the ns `TestApplication.generics`, but I don't think it really matters. Anyway, here's the actual error message : `Impossible de charger le type 'TestApplication.generics.ISomething_Impl\`1' à partir de l'assembly 'TestApplication_Accessor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', car il déclare un paramètre de type covariant ou contravariant et il n'est pas une interface ou un délégué.` – Yanick Rochon Nov 29 '12 at 20:00
  • Hmm, that is realy interesting, Are you in unit test envirement? in which type of application are you checking? I wonder if we meet this bug in vs2010. http://connect.microsoft.com/VisualStudio/feedback/details/609849/vs2010-c-unit-tests-wizard-generates-code-with-private-accessor-errors-for-covariant-generic-parameters – Arsen Mkrtchyan Nov 29 '12 at 20:43
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/20351/discussion-between-arsenmkrt-and-yanick-rochon) – Arsen Mkrtchyan Nov 29 '12 at 20:47
  • Yes! This is exactly it! I had a unit test project attached and it failed to compiled. It managed to work when I unloaded the project from the solution... wow... This is probably one of my most ridiculous error/workaround (wtf? moment) with .Net that I've had so far. It probably even beats my previous "there is an unknown error caused an unknown exception in an unknown source" (VS2003) :) – Yanick Rochon Nov 29 '12 at 21:31
2

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>());
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • This is interesting. I thought all objects inherited from `object`. And having two interfaces of the same name, one being generic and not the other one... is plain weird to me. – Yanick Rochon Nov 29 '12 at 16:16
  • However the problem here is that the method `GetObject` needs to have a specific implementation for each concrete type, thus why the generics, and I need to set a concrete implementation to some non-generic class (`ISomething` must be inter-changeable inside `SomeContainer`) – Yanick Rochon Nov 29 '12 at 20:19