Llamar a un método estático en un parámetro de tipo genérico

107

Esperaba hacer algo como esto, pero parece ser ilegal en C #:


public Collection MethodThatFetchesSomething<T>()
    where T : SomeBaseClass
{
    return T.StaticMethodOnSomeBaseClassThatReturnsCollection();
}

Recibo un error en tiempo de compilación: "'T' es un 'parámetro de tipo', que no es válido en el contexto dado".

Dado un parámetro de tipo genérico, ¿cómo puedo llamar a un método estático en la clase genérica? El método estático debe estar disponible, dada la restricción.

Remi Despres-Smyth
fuente

Respuestas:

58

En este caso, debe llamar directamente al método estático en el tipo restringido. C # (y CLR) no admiten métodos estáticos virtuales. Entonces:

T.StaticMethodOnSomeBaseClassThatReturnsCollection

... no puede ser diferente a:

SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection

Pasar por el parámetro de tipo genérico es una indirección innecesaria y, por lo tanto, no es compatible.

JaredPar
fuente
25
Pero, ¿y si enmascarara su método estático en una clase secundaria? public class SomeChildClass: SomeBaseClass {public new static StaticMethodOnSomeBaseClassThatReturnsCollection () {}} ¿Podrías hacer algo para acceder a ese método estático desde un tipo genérico?
Hugo Migneron
2
Mira la respuesta de Joshua Pech a continuación, creo que funcionaría en ese caso.
Remi Despres-Smyth
1
¿ return SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection();Funcionaría? Si es así, es posible que desee agregar eso a su respuesta. Gracias. Funcionó para mí. En mi caso, mi clase tenía un parámetro de tipo, así que lo hice return SomeBaseClass<T>.StaticMethodOnSomeBaseClassThatReturnsCollection();y funcionó.
toddmo
27

Para desarrollar una respuesta anterior, creo que la reflexión está más cerca de lo que quieres aquí. Podría dar 1001 razones por las que debería o no debería hacer algo, solo responderé a su pregunta tal como se me pide. Creo que deberías llamar al método GetMethod en el tipo de parámetro genérico e ir desde allí. Por ejemplo, para una función:

public void doSomething<T>() where T : someParent
{
    List<T> items=(List<T>)typeof(T).GetMethod("fetchAll").Invoke(null,new object[]{});
    //do something with items
}

Donde T es cualquier clase que tenga el método estático fetchAll ().

Sí, soy consciente de que esto es terriblemente lento y puede fallar si someParent no fuerza a todas sus clases secundarias a implementar fetchAll pero responde la pregunta tal como se le preguntó.

Joshua Pech
fuente
2
No, en absoluto. JaredPar lo hizo absolutamente bien: T.StaticMethodOnSomeBaseClassThatReturnsCollection donde T: SomeBaseClass no es diferente a simplemente indicar SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection.
Remi Despres-Smyth
2
Esto es lo que necesitaba, funciona con un método estático
myro
Esta fue la respuesta que necesitaba porque no tenía el control de las clases y la clase base.
Tim
8

La única forma de llamar a dicho método sería a través de la reflexión. Sin embargo, parece que podría ser posible envolver esa funcionalidad en una interfaz y usar un patrón IoC / factory / etc. basado en instancias.

Marc Gravell
fuente
5

Parece que está intentando utilizar genéricos para evitar el hecho de que no existen "métodos estáticos virtuales" en C #.

Desafortunadamente, eso no va a funcionar.

Brad Wilson
fuente
1
No estoy trabajando por encima de una capa DAL generada. Todas las clases generadas heredan de una clase base, que tiene un método FetchAll estático. Estoy tratando de reducir la duplicación de código en mi clase de repositorio con una clase de repositorio "genérica": mucho código repetido, excepto por la clase concreta utilizada.
Remi Despres-Smyth
1
Entonces, ¿por qué no llamas a SomeBaseClass.StaticMethod ... ()?
Brad Wilson
Lo siento, no me expliqué bien en el comentario anterior. FetchAll se define en la base, pero se implementa en las clases derivadas. Necesito llamarlo en la clase derivada.
Remi Despres-Smyth
7
Si es un método estático, la base lo define e implementa. No existe un método estático virtual / abstracto en C #, y no existe una anulación para ello. Sospecho que simplemente lo ha vuelto a declarar, que es muy diferente.
Marc Gravell
1
Sí, tienes razón: había hecho suposiciones inválidas aquí. Gracias por la discusión, me ayudó a encaminarme por el buen camino.
Remi Despres-Smyth
2

A partir de ahora, no puedes. Necesita una forma de decirle al compilador que T tiene ese método y, actualmente, no hay forma de hacerlo. (Muchos están presionando a Microsoft para que expanda lo que se puede especificar en una restricción genérica, por lo que tal vez esto sea posible en el futuro).

James Curran
fuente
1
El problema es que debido a que los genéricos los proporciona el tiempo de ejecución, esto probablemente significaría una nueva versión de CLR, que han evitado (en gran medida) desde la 2.0. Sin embargo, quizás tengamos uno nuevo ...
Marc Gravell
2

Aquí, publico un ejemplo que funciona, es una solución

public interface eInterface {
    void MethodOnSomeBaseClassThatReturnsCollection();
}

public T:SomeBaseClass, eInterface {

   public void MethodOnSomeBaseClassThatReturnsCollection() 
   { StaticMethodOnSomeBaseClassThatReturnsCollection() }

}

public Collection MethodThatFetchesSomething<T>() where T : SomeBaseClass, eInterface
{ 
   return ((eInterface)(new T()).StaticMethodOnSomeBaseClassThatReturnsCollection();
}
rodrijp
fuente
2
¿Esto me da un error de sintaxis? ¿Qué public T : SomeBaseClasssignifica?
Eric
Si su clase tiene un método de instancia someInstanceMethod (), siempre puede llamarlo haciendo (new T ()). SomeInstanceMethod (); - pero esto es llamar a un método de instancia - la pregunta era cómo llamar a un método estático de la clase.
timothy
2

Solo quería decir que a veces los delegados resuelven estos problemas, según el contexto.

Si necesita llamar al método estático como una especie de fábrica o método de inicialización, entonces puede declarar un delegado y pasar el método estático a la fábrica genérica relevante o lo que sea que necesite esta "clase genérica con este método estático".

Por ejemplo:

class Factory<TProduct> where TProduct : new()
{
    public delegate void ProductInitializationMethod(TProduct newProduct);


    private ProductInitializationMethod m_ProductInitializationMethod;


    public Factory(ProductInitializationMethod p_ProductInitializationMethod)
    {
        m_ProductInitializationMethod = p_ProductInitializationMethod;
    }

    public TProduct CreateProduct()
    {
        var prod = new TProduct();
        m_ProductInitializationMethod(prod);
        return prod;
    }
}

class ProductA
{
    public static void InitializeProduct(ProductA newProduct)
    {
        // .. Do something with a new ProductA
    }
}

class ProductB
{
    public static void InitializeProduct(ProductB newProduct)
    {
        // .. Do something with a new ProductA
    }
}

class GenericAndDelegateTest
{
    public static void Main()
    {
        var factoryA = new Factory<ProductA>(ProductA.InitializeProduct);
        var factoryB = new Factory<ProductB>(ProductB.InitializeProduct);

        ProductA prodA = factoryA.CreateProduct();
        ProductB prodB = factoryB.CreateProduct();
    }
}

Desafortunadamente, no puede hacer cumplir que la clase tenga el método correcto, pero al menos puede hacer cumplir en tiempo de compilación que el método de fábrica resultante tenga todo lo que espera (es decir, un método de inicialización con exactamente la firma correcta). Esto es mejor que una excepción de reflexión en tiempo de ejecución.

Este enfoque también tiene algunos beneficios, es decir, puede reutilizar métodos init, hacer que sean métodos de instancia, etc.

Amir Abiri
fuente
1

Debería poder hacer esto usando la reflexión, como se describe aquí

Debido a que el enlace está muerto, encontré los detalles relevantes en la máquina de retorno:

Suponga que tiene una clase con un método genérico estático:

class ClassWithGenericStaticMethod
{
    public static void PrintName<T>(string prefix) where T : class
    {
        Console.WriteLine(prefix + " " + typeof(T).FullName);
    }
}

¿Cómo puedes invocar este método usando relección?

Resulta muy fácil ... Así es como se invoca un método genérico estático usando la reflexión:

// Grabbing the type that has the static generic method
Type typeofClassWithGenericStaticMethod = typeof(ClassWithGenericStaticMethod);

// Grabbing the specific static method
MethodInfo methodInfo = typeofClassWithGenericStaticMethod.GetMethod("PrintName", System.Reflection.BindingFlags.Static | BindingFlags.Public);

// Binding the method info to generic arguments
Type[] genericArguments = new Type[] { typeof(Program) };
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments);

// Simply invoking the method and passing parameters
// The null parameter is the object to call the method from. Since the method is
// static, pass null.
object returnValue = genericMethodInfo.Invoke(null, new object[] { "hello" });
johnc
fuente
El vínculo está muerto.
Necronomicron