Perdóname si esto ya se ha pedido. Lo busqué pero no pude encontrar nada.

Parece que el compilador se está confundiendo con este código

public abstract class C1
{
    public int c1Prop;
}

public class C2 : C1
{
    public int c2Prop;
}

public abstract class P1
{
    public abstract void Run<T>(T c) where T : C1;
}

public class P2 : P1
{
    public override void Run<C2>(C2 c) 
    {
        c.c1Prop = 1; //Is recognized
        c.c2Prop = 2; //Is NOT recognized and is an error
    }
}

No entiendo por qué esto no funcionaría a nivel de función. Dado que C2 extiende C1, no está violando la verificación where, pero el tipo C2 aún no se reconoce en el método anulado.

Para algunos antecedentes, estoy usando un patrón similar en Unity ScriptableObject que no funciona bien con clases genéricas, por lo que no puedo mover el tipo genérico al nivel de clase. Sin embargo, moverlo hacia arriba parece solucionar el problema.

Otra posible solución que se me ocurrió es deshacernos del método genérico todos a favor de la conversión. Sin embargo, eso no parece tan expresivo como un genérico. También lanzar c en varios lugares a lo largo del método Run sería molesto.

0
Jim S. 13 sep. 2018 a las 19:58

4 respuestas

La mejor respuesta

Cuando dices void Run<C2>(C2 c) estás diciendo que C2 es un tipo genérico , es no el tipo concreto C2. Para aclarar esto, cambie C2 a T:

public override void Run<T>(T c)
{
    c.c1Prop = 1; //Is recognized
    c.c2Prop = 2; //Is NOT recognized and is an error
}

La razón por la que puede acceder a c1Prop es la restricción de tipo where T : C1 anteriormente en la jerarquía.

Una forma de evitar esto sería hacer que P1 sea genérico:

public abstract class P1<T> where T : C1
{
    public abstract void Run(T c);
}

Lo que hace que P2 se vea así:

public class P2 : P1<C2>
{
    public override void Run(C2 c)
    {
        c.c1Prop = 1;
        c.c2Prop = 2;
    }
}
2
DavidG 13 sep. 2018 a las 17:07

Si bien apoyo el enfoque de @ DavidG, si no desea utilizar una declaración genérica en su clase, puede usar este enfoque

public interface IC1
{
    int prop1 { get; set; }
}

public interface IC2
{
    int prop2 { get; set; }
}

public abstract class C1 : IC1
{
    #region Implementation of IC1

    public int prop1 { get; set; }

    #endregion
}

public class C2 : C1, IC2
{
    #region Implementation of IC2

    public int prop2 { get; set; }

    #endregion
}

public abstract class P1
{
    public abstract void Run<T>(T c) where T : IC1, IC2;
}

public class P2 : P1
{
    public override void Run<T>(T c)
    {
        c.prop1 = 1; 
        c.prop2 = 2; 
    }
}
0
Trae Moore 13 sep. 2018 a las 17:15

La firma de su método indica que el método debe ser genérico y no puede anularlo solo para algún tipo concreto, puede hacer lo siguiente:

    public class P2 : P1
    {
        public override void Run<T>(T c)
        {
            c.c1Prop = 1; //Is recognized since you have where T : C1 clause
            var c2 = c as C2;
            if (c2 != null)
            {
                c2.c2Prop = 2;
            }
        }
    }
0
Guru Stron 13 sep. 2018 a las 17:04

La causa inicial de confusión es que dentro de su Run anulación, C2 es un parámetro de tipo - no es la clase llamada {{X2 }}. Es útil dejar eso claro dejándolo como T en la declaración del método de anulación:

public class P2 : P1
{
    // Changed type parameter name from C2 to T for clarity
    public override void Run<T>(T c) 
    {
        c.c1Prop = 1;
        c.c2Prop = 2;
    }
}

Es un código absolutamente equivalente, pero es más claro lo que está sucediendo.

Ahora T está limitado con where T : C1, que es cómo funciona c.c1Prop, pero es completamente factible que c no será un C2 . Por ejemplo, podría escribir:

class OtherC1 : C1 {}

P2 p2 = new P2();
p2.Run(new OtherC1());

Eso claramente no puede funcionar con su código actual: no hay c2Prop en OtherC1.

Parece que puede desear que P1 sea genérico en lugar del método Run. Podrías tener:

public abstract class P1<T> where T : C1
{
    public abstract void Run(T c);
}

public class P2 : P1<C2>
{
    public override void Run(C2 c) 
    {
        c.c1Prop = 1; //Is recognized
        c.c2Prop = 2; //Is NOT recognized and is an error
    }
}

Eso compilará, y todo el código sabrá que puede solo proporcionar una C2 (o una clase más derivada) a P2.Run. Por lo tanto, nuestro ejemplo anterior con OtherC1 ya no se compilará (que es lo que queremos).

1
Jon Skeet 13 sep. 2018 a las 17:13