Search code examples
c#genericscastinggeneric-constraints

"Cannot convert from out T to Component", even though T is restrained to be a Component


I'm creating a TryGetComponent(Type type, out Component component) method, and then adding a TryGetComponent<T>(out T component) overload for convenience:

public bool TryGetComponent(Type type, out Component component)
{
    component = null;
    if (type.IsAssignableFrom(typeof(Component)))
    {
        throw new ArgumentException($"Must get component using a type that is assignable to {nameof(Component)}: Parameter type was '{type}'", nameof(type));
    }
    return Components.TryGetValue(type, out component);
}

public bool TryGetComponent<T>(out T component) where T : Component
{
    return TryGetComponent(typeof(T), out component);
}

But I get the following compile error regarding ```out component`` in the second function.

Cannot convert from out T to out component

Adding an explicit cast to T gives the following error

The out parameter must be assigned before control leaves the current method.

I'm almost certain I've done this exact pattern before. No idea why it's not compiling.


Solution

  • As John Wu said in his comment, every T is a Component, but not every Component is a T.

    It looks like you're trying to achieve the following:

    • One method to "TryGet" a component
    • A second method to "TryGet" a component of a particular derived type T, and either return false or throw if that component is not a T.

    (whether you want to return false or throw is not clear because the implementation is not complete. You can modify my code to do whichever you prefer)

    In your second method, you're trying to simply call the first method, but you need extra logic to check if that component is a T and than handle that.


    Update: @Charlieface pointed out that the inner call specifies typeof(T) anyway, so we can assume the component is a T. But this can't be known at compile time, so we still need logic to cast it:

    public bool TryGetComponent<T>(out T component) where T : Component
    {
        component = null;
    
        var success = TryGetComponent(typeof(T), out var found);
        if (!success) {
            return false; // Component not found; return false
        }
    
        component = (T)t;
        return true; // Component found; return true
    }
    

    Old example:
    So, something like:

    public bool TryGetComponent<T>(out T component) where T : Component
    {
        component = null;
    
        var success = TryGetComponent(typeof(T), out var found);
        if (!success) {
            return false; // Component not found; return false
        }
    
        if (found is not T t) {
            return false; // Component wrong type; return false or throw
        }
    
        component = t;
        return true; // Component found; return true
    }