Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics in C# - Cannot convert 'classname' to 'TGenericClass'

Tags:

c#

generics

UPDATE: This isn't about getting it to compile. The question is, why does the C# compiler allow the cast when using an interface, but it can't figure out the type when I use a class that implements the same interface.

I am getting the following error:

Cannot convert type 'Amber.BLL.iWeb.Session.AppSession' to 'TService'   

Here is the code:

public override TService GetService<TService>() 
{
    if ( typeof( TService ) == typeof( IAppSession ) )
    {
        AppSession session = new AppSession();
        return (TService) session;
    }
    throw new Exception( String.Format( 
        "iWebFactoryProvider cannot create services of type '{0}'.", 
        typeof( TService ).Name ) );
}

As it so happens, the AppSession class implements the IAppSession interface. If I change the line of code that instantiates AppSession to use the interface, like this:

IAppSession session = new AppSession();

suddenly everything compiles fine. I also note that it compiles fine if I do this:

AppSession session = new AppSession();
return (TService) (IAppSession) session;

In case it matters, the GetService() is overriding a method whose signature is declared like this:

public virtual TService GetService<TService>() where TService : class

In short, I can't figure out what the rules should be here so I can know how to avoid this situation in the future. Why was the compiler happy to cast the interface, but not happy to cast the interface's implementing class?

I note that this question is asking about a similar issue, but the answer isn't detailed enough for me to understand how it applies to my situation.

like image 405
Katie Kilian Avatar asked Feb 29 '12 15:02

Katie Kilian


People also ask

Are there generic types in C?

Unlike C++ and Java, C doesn't support generics. How to create a linked list in C that can be used for any data type? In C, we can use a void pointer and a function pointer to implement the same functionality. The great thing about void pointer is it can be used to point to any data type.

What are generics used for?

In a nutshell, generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs.

What are generics in language?

Generics are statements such as “tigers are striped”, “a duck lays eggs”, “the dodo is extinct”, and “ticks carry Lyme disease”. Generics express generalizations, but unlike quantified statements, generics do not carry information about how many members of the kind or category have the property.

What is generic and template?

Generics are generic until the types are substituted for them at runtime. Templates are specialized at compile time so they are not still parameterized types at runtime. The common language runtime specifically supports generics in MSIL.


2 Answers

Why does the C# compiler allow the cast when using an interface, but it can't figure out the type when I use a class that implements the same interface?

Good question. Consider the following:

public interface I {}
public class D {} // Note that D does not even implement I!
public class E
{
    public static M<T>(T t)
    {
        D d1 = (D)t; // Illegal
        D d2 = (D)(object)t; // Legal
        D d3 = (D)(I)t; // Legal
    }    
}

Let's break your question up into three questions.

Why is the cast directly from T to D illegal?

Suppose it were legal. Then E.M<D>(new D()) would work just fine; we'd cast the T to D and in fact it is a D, so no problem.

Now suppose we create an entirely different assembly with:

class C 
{
    public static explicit operator D(C c) { whatever }
}

And you call E.M<C>(new C()) in that assembly.. What do you reasonably expect to happen? You have an object of type C, it is being cast to D, and there is an explicit conversion operator right there from C to D. Most people would reasonably expect that the explicit conversion operator would be called.

But how on earth is the compiler supposed to realize when compiling the body of M that someone in the future might create a class C in a completely different assembly? The compiler has no way to emit the call to the conversion operator when compiling M. So we have three choices:

  1. Make cast operators sometimes use explicit conversion operators and sometimes not, depending on whether you're in a generic or not.
  2. Make cast operators start the compiler again at runtime to look for explicit conversion operators that might have been added in different assemblies after the original code was compiled.
  3. Disallow the cast in the first place.

In short, our choices are (1) make generics inconsistent, (2) make generics slow and unpredictable, or (3) disallow a feature that is already working against genericity. This is an easy choice to make; we chose (3).

If you want (2), you can have it in C# 4; dynamic starts the compiler again at runtime and works out whether there is an explicit conversion operator.

Why is the cast indirectly from T to D via object legal?

Because now no user-defined conversion can be relevant; there is never a user-defined conversion from object to anything.

Why is the cast indirectly from T to D via I legal?

Because now no user-defined conversion can be relevant; there is never a user-defined conversion from an interface to anything.

Bonus question:

But D does not even implement I! What's up with that?

A derived class of D might:

class F : D, I {}
...
E.M<D>(new F());

Now t can be cast to I because it might implement I, and I can be cast to D because it might be F.

If D were sealed then it would not be legal to cast from I to D because then there could not possibly be a derived F type.

like image 89
Eric Lippert Avatar answered Sep 30 '22 02:09

Eric Lippert


Have you tried adding a constraint for IAppSession?

public virtual TService GetService<TService>() where TService : IAppSession, class

That linked question is exactly the same issue. The compiler doesn't know that TService can be an AppSession.

like image 22
jrummell Avatar answered Sep 30 '22 03:09

jrummell