4

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);
    }
bitbonk
  • 48,890
  • 37
  • 186
  • 278

7 Answers7

4

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
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 1
    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 May 04 '10 at 14:05
  • 2
    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 May 04 '10 at 14:06
  • 2
    You should make the return type NodeBase rather than a generic type. – Eric Mickelsen May 04 '10 at 14:07
  • 1
    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(), GetNode() and GetNode(). Only one of them would be legal: GetNode(), so there's no reason for this to be a generic method. – Eric Mickelsen May 04 '10 at 14:13
  • @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 Rothermel May 04 '10 at 14:14
  • 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 May 04 '10 at 14:17
  • @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 May 04 '10 at 14:17
  • @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 Rothermel May 04 '10 at 14:19
  • @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 May 04 '10 at 14:21
  • @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 May 04 '10 at 14:24
  • @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() 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 Rothermel May 04 '10 at 14:27
  • 1
    @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 May 04 '10 at 14:29
  • @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 Rothermel May 04 '10 at 14:30
  • @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 Rothermel May 04 '10 at 14:32
  • @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 May 04 '10 at 14:43
  • @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 Rothermel May 04 '10 at 14:49
  • @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 May 04 '10 at 16:32
  • @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 Rothermel May 04 '10 at 16:37
3

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
  • 125,917
  • 54
  • 300
  • 447
0

Ah, that moment you wish the language had some kinda static polymorphism. Type checking in a generic method is not very cool. This can be a better solution if your requirements let:

public abstract class NodeBase
{
    public abstract NodeBase GetNode();
}

public class SpecialNode : NodeBase
{
    public override NodeBase GetNode()
    {
        return ThisStaticClass.MySpecialNode;
    }
}

public class OtherSpecialNode : NodeBase
{
    public override NodeBase GetNode()
    {
        return ThisStaticClass.MyOtherSpecialNode;
    }
}

//and in your generic method, just:
public static T GetNode<T>() where T : NodeBase, new()
{
    return (T)new T().GetNode();
}

This suffer from a disadvantage that you have to expose a parameterless constructor. In case that's undesirable, may be a slightly better approach is to push the generic call one layer backward, and ask your static class itself to do the work for you. Changing static class definition to something like:

public static T GetNode<T>() where T : NodeBase
{
    return ThisStaticClass<T>.GetNode();
}

//in which case ThisStaticClass<T> have awareness of 
//parameterless new() constructor of T class, which still need not be good enough

You can get it a bit more strongly typed by going one generic level deeper.

public abstract class NodeBase<T> where T : NodeBase<T>
{
    public abstract T GetNode();
}

public class SpecialNode : NodeBase<SpecialNode>
{
    public override SpecialNode GetNode()
    {
        return ThisStaticClass.MySpecialNode;
    }
}

public class OtherSpecialNode : NodeBase<OtherSpecialNode>
{
    public override OtherSpecialNode GetNode()
    {
        return ThisStaticClass.MyOtherSpecialNode;
    }
}

//avoids cast
public static T GetNode<T>() where T : NodeBase<T>, new()
{
    return new T().GetNode();
}
nawfal
  • 70,104
  • 56
  • 326
  • 368
0

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
  • 28,719
  • 15
  • 79
  • 106
0

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 Rothermel
  • 9,436
  • 8
  • 62
  • 81
-1

Why not just do the following:

return (T)MySpecialNode;

What version of .NET?

Ian P
  • 12,840
  • 6
  • 48
  • 70
-1

See my answer from a previous post

Using Generics to return a literal string or from Dictionary<string, 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

Community
  • 1
  • 1
Mike
  • 5,918
  • 9
  • 57
  • 94