Contravariance/Covariance - cannot convert class to interface

I have the following code

public interface IInterface
{
}
public class GenericClass<TSomeClass>
    where TSomeClass : class
{
    public TSomeClass SomeMethod(TSomeClass someClass = null)
    {
        return SomeClass.SomeClassStaticInstance;                   //ERROR:Cannot implicitly convert type 'SomeClass' to 'TSomeClass'
        return (TSomeClass)SomeClass.SomeClassStaticInstance;       //ERROR:Cannot convert type 'SomeClass' to 'TSomeClass'
        return SomeClass.SomeClassStaticInstance as TSomeClass;     //Works when "where TSomeClass : class" clause added
    }
}
public class SomeClass : IInterface
{
    public static SomeClass SomeClassStaticInstance = new SomeClass();
}

It generates the compile time errors noted in the comments on the appropriate lines.

I would like to know why I can't just use the first line that generates an error? SomeClass implements IInterface but I have to mess around with the as keyword.

I've tried changing GenericClass<TSomeClass> to GenericClass<out TSomeClass> but then I get another compile time error Only interface and delegate type parameters can be specified as variant. which persists, even if i remove the where TSomeClass : class clause.

What am I missing ... it obviously works because I can 'force it to' using the where TSomeClass : class clause and the return SomeClass.SomeClassStaticInstance as TSomeClass; statement!

I do actually need the where TSomeClass : class otherwise I get yet another compile time error with TSomeClass someClass = null ... A value of type '<null>' cannot be used as a default parameter because there are no standard conversions to type 'TSomeClass'.

So it's basically compile time errors all the way down! Thanks.

Upvotes: 0

Views: 240

Answers (3)

Johnathan Barclay
Johnathan Barclay

Reputation: 20353

At the moment your only constraint is that TSomeClass is a class, so if you were to instantiate GenericClass with a type that isn't SomeClass, or a type that derives from it, return SomeClass.SomeClassStaticInstance; clearly isn't going to work.

For example:

var genericClass = new GenericClass<SomeRandomClass>(); // fine
var randomClass = genericClass.SomeMethod(new SomeRandomClass()); // not fine, a SomeClass is returned!

I'm not sure where IInterface currently fits into all this?

Maybe you want SomeMethod to return an object that implements IInterface; if so, you need to further constrain your generic type, and have your method return an IInterface:

public class GenericClass<TSomeClass> where TSomeClass : class, IInterface
{
    public IInterface SomeMethod(TSomeClass someClass = null)
    {
        return SomeClass.SomeClassStaticInstance; //This now compiles!
    }
}

As for why return SomeClass.SomeClassStaticInstance as TSomeClass; worked originally, this was merely a coincidence.

as is a safe cast operator, which returns null if the attempted cast fails. As you constrained TSomeClass to be a class, a null return value would be valid. You would always return null however, if the generic type you specified wasn't SomeClass.

Upvotes: 1

Paddy
Paddy

Reputation: 33857

I believe that this would work:

public interface IInterface
{
}

public class GenericClass<TSomeClass>
    where TSomeClass : IInterface
{
    //Not sure what the input parameter is for?
    public IInterface SomeMethod(TSomeClass someClass = null)
    {
        return SomeClass.SomeClassStaticInstance;                  
    }
}

public class SomeClass : IInterface
{
    public static IInterface SomeClassStaticInstance = new SomeClass();
}

However as noted by others, the question here is really what generics are being used for in this instance - is there possibly a better choice of how to structure your code?

Very hard to tell what the purpose of this would be from the structure you have given us.

Upvotes: 1

vendettamit
vendettamit

Reputation: 14677

I would like to know why I can't just use the first line that generates an error? SomeClass implements IInterface but I have to mess around with the as keyword.

You have to use (TSomeClass)YourInstance cast as language is designed to prevent any assumptions on casting generic arguments. Read more.

Because the implicit conversion on type of argument TSomeClass can not be predicted unless you put a constraint of less inherited type e.g. interface. You should change your constraint to where TSomeClass : IInterface for variance.

Using as key worked because it will return null if the type is incompatible, which is why you need class constraint. After changing your constraint to interface the as statement will become invalid.

In your factory method you should return IInterface so the generic type argument can accept a direct cast.

Upvotes: 1

Related Questions