tags:

views:

111

answers:

6

why do I get a compiler error in the following code stating: Cannot implicty convert type SpecialNode to T even though T must derive from NodeBase as I defined in the where clause and even though SpecialNode actually derived from NodeBase?

    public static T GetNode<T>() where T : NodeBase
    {
        if (typeof(T) == typeof(SpecialNode))
        {
            return ThisStaticClass.MySpecialNode; // <-- compiler error
        }
        if (typeof(T) == typeof(OtherSpecialNode))
        {
            return ThisStaticClass.MyOtherSpecialNode; // <-- compiler error
        }
        ...
        return default(T);
    }
A: 

Why not just do the following:

return (T)MySpecialNode;

What version of .NET?

Ian P
That won't help.
SLaks
+2  A: 

The compiler doesn't read your if check to realize that in this particular line, T must be SpecialNode.

You need to cast to NodeBase first, like this:

return (T)(NodeBase)MySpecialNode;

You need to casts because (as far as the compiler knows) T might be MyOtherSpecialNode, and you cannot cast a MyOtherSpecialNode to MySpecialNode.

EDIT: You can do it with a single cast like this:

NodeBase retVal;

if (typeof(T) == typeof(SpecialNode))
    retVal = MySpecialNode;
else if (typeof(T) == typeof(OtherSpecialNode))
    retVal = MyOtherSpecialNode;

return (T)retVal;
SLaks
Just because it's a `NodeBase` doesn't mean it's a `T` – it could be an instance of a _different_ `NodeBase` class that cannot be casted to `T`. You're right that there is an implicit conversion of `MySpecialNode` to `NodeBase`, but the compiler won't use it implicitly to allow another cast. If you make a variable of type `NodeBase`, assign `MySpecialNode` to it, then return the variable, you'll only need one cast, as in my edit.
SLaks
But the compiler does not know that T is NodeBase - only that it derives from NodeBase. T could be OtherSpecialNode, which would make returning a SpecialNode illegal.
Eric Mickelsen
You should make the return type NodeBase rather than a generic type.
Eric Mickelsen
But you have three types that we know of that could satisfy T: NodeBase, SpecialNode and OtherSpecialNode. Since this is a generic, there are now three possible versions of this method: GetNode<NodeBase>(), GetNode<SpecialNode>() and GetNode<OtherSpecialNode>(). Only one of them would be legal: GetNode<NodeBase>(), so there's no reason for this to be a generic method.
Eric Mickelsen
@tehMick: If the return type is NodeBase it'll probably have to be cast later anyway. The generic version could cut down on code, but it could also have unneeded casts.
Nelson
More generally, how can you expect the compiler to figure out what the return type T really is AFTER the method is executing? Generics are effectively a method of making many copies of a method that operate on different types - the compiler has to figure out which copy is right BEFORE running it, and the code inside has to be valid for every possible copy.
Eric Mickelsen
@Nelson: specifying the generic type is no easier than casting. It's effectively the same thing since we're just casting inside the method based on that generic type parameter. However, if this was a generic class that had one node type or the other, this would make a lot more sense.
Eric Mickelsen
@tehMick: The type IS known before running it... The type is given to the generic, then a matching type is returned. Or am I missing something?
Nelson
@Nelson: the generic type is known where the method is called. When the compiler is compiling the method itself, it doesn't know what T will be.
Eric Mickelsen
@Nelson: For instance, how do we know there isn't code that will be written tomorrow by someone using this like a library, wherein a class will be derived from NodeBase (Call it WeirdNode) which is used as the type parameter. Then what?! We can't return the default NodeBase because it is not a WeirdNode, which is the return type of the method.
Eric Mickelsen
@tehMick: Yes, but if you don't make it generic then you need to pass the type as an argument AND cast it: (SpecialNode)GetNode(typeof(SpecialNode)) versus GetNode<SpecialNode>() The "simplest" is to reference them directly: ThisStaticClass.MySpecialNode, no cast needed, but it might not make sense with how the classes are organized.
Nelson
@Nelson: If it's not generic, there's no type parameter. It's just (SpecialNode)GetNode(). Or if it's really that important, just right a GetSpecialNode(). If we had some context, the right choice would be clearer.
Eric Mickelsen
@tehMick: then it will return default(WeirdNode). Even in the non-generic form it still wouldn't know how to find WeirdNode in ThisStaticClass. This is basically a "factory" method which uses pre-existing instances. The factory has to know about all the types it will provide if it will do any custom handling, otherwise it has to handle it generically like default(T). (Note: "factory" is probably not the best use of the concept in this case)
Nelson
@tehMick: You're right with the cast if there is only one node. If there are multiple nodes with unique (non-repeated) types, then you would have to specify it as a parameter. More context would help. We're both making different assumptions.
Nelson
@Nelson: Agreed. Good point about default(T), but it still won't be able to determine whether returning those other objects is legal until the method is used with a particular type parameter - not when the generic is compiled.
Eric Mickelsen
@tehMick: Wouldn't the "where T : NodeBase" ensure it's legal at compile time? I actually find this amusing. The answer was accepted already and here we are rambling on... :)
Nelson
Why was this downvoted?
SLaks
@Nelson: I know. :-) As the compiler is looking at the method, it can't assume that T isn't SpecialNode or OtherSpecialNode, which would make one or the other return illegal. In the case that T is SpecialNode, `return (T)MyOtherSpecialNode` compiles to `return (SpecialNode)MyOtherSpecialNode`, which can never be a valid cast. Does that make sense. The whole point on SO is to learn something, so discussion by way of comments can be helpful.
Eric Mickelsen
@tehMick: But you're not providing the node... It's never going to try to cast from one type to another, unless ThisStaticClass.MySpecialNode is not SpecialNode, etc. In case the method doesn't provide the correct type, it does a default(T) which should always work. I think... :)
Nelson
A: 

The problem is that the function may be called with a type parameter T, which derives from NodeBase but not from SpecialNode. The compiler doesn't check the semantics of the if statement, so it doesn't know, that T has to be a specialNode.

You would need to use an explicit cast to T in order to satisfy your compiler.

MartinStettner
+2  A: 

You might see that a condition has been logically met, but the compiler does not "remember" this.

public static T GetNode<T>() where T : NodeBase
{
    if (typeof(T) == typeof(SpecialNode)) // OK, you now know T is SpecialNode
    {
        // the compiler still insists on returning a T,
        // and will not assume that MySpecialNode is a T
        return MySpecialNode;
    }

    // ...

    return default(T);
}

It's true what others have already said: you must cast MySpecialNode: (T)(NodeBase)MySpecialNode (which you can do safely, because you have already checked that T is SpecialNode).

It's easy to think of this as a shortcoming of the compiler; but this is just a mistake stemming from how obvious it seems that MySpecialNode is a T. Suppose I had a method like this:

public T Get<T>() {
    if (typeof(T).FullName.Equals("System.Int32"))
        return 5;
    else
        return default(T);
}

Should this compile? I should hope not; the compiler needs to guarantee that it's returning an object of type T, and it can't be sure that 5 will meet that requirement just from some bizarre check that I the developer have performed. (Yes, I know that T is int, but I would not expect any reasonable compiler to determine that from a comparison of the System.Type.FullName property.)

Checking if (typeof(T) == typeof(SpecialNode)) is really not so different from that.

Dan Tao
A: 

See my answer from a previous post

http://stackoverflow.com/questions/2691374/using-generics-to-return-a-literal-string-or-from-dictionarystring-object

but the answer is

return (T)MySpecialNode

Because even you do the if check the compiler does not so you have to recast to T

Mike
This won't compile. See my comment on my answer.
SLaks
That's because you're casting from `object`, which can be casted to any `T`.
SLaks
A: 

You could also do:

public static T GetNode<T>() where T : NodeBase
{
    T result;

    result = ThisStaticClass.MySpecialNode as T;
    if (result != null) return result;

    result = ThisStaticClass.MyOtherSpecialNode as T;
    if (result != null) return result;

    return default(T);
}

Edit: If the static classes are already null, this probably wouldn't have the intended effect.

Nelson
This won't compile either. You mean `as T`
SLaks
Yeah, my bad...
Nelson